Tokeny trafiają do Twojego portfela natychmiast po potwierdzeniu transakcji. Odbiorca nie musi podejmować żadnych działań. Solana automatycznie zwiększa saldo token account odbiorcy i zmniejsza saldo nadawcy. W tym przewodniku omawiamy przydatne narzędzia do sprawdzania salda token account oraz monitorowania przychodzących płatności.
Sprawdzanie salda tokenów
Sprawdź swoje saldo stablecoinów za pomocą metody RPC
getTokenAccountBalance:
import { createSolanaRpc, address, type Address } from "@solana/kit";import {findAssociatedTokenPda,TOKEN_PROGRAM_ADDRESS} from "@solana-program/token";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const USDC_MINT = address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");async function getBalance(walletAddress: Address) {// Derive the token account addressconst [ata] = await findAssociatedTokenPda({mint: USDC_MINT,owner: walletAddress,tokenProgram: TOKEN_PROGRAM_ADDRESS});// Query balance via RPCconst { value } = await rpc.getTokenAccountBalance(ata).send();return {raw: BigInt(value.amount), // "1000000" -> 1000000nformatted: value.uiAmountString, // "1.00"decimals: value.decimals // 6};}
Monitorowanie przychodzących transferów
Subskrybuj swój token account, aby otrzymywać powiadomienia o płatnościach w
czasie rzeczywistym, korzystając z metody RPC accountNotifications:
const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.mainnet-beta.solana.com");async function watchPayments(tokenAccountAddress: Address,onPayment: (amount: bigint) => void) {const abortController = new AbortController();const subscription = await rpcSubscriptions.accountNotifications(tokenAccountAddress, {commitment: "confirmed",encoding: "base64"}).subscribe({ abortSignal: abortController.signal });let previousBalance = 0n;for await (const notification of subscription) {const [data] = notification.value.data;const dataBytes = getBase64Codec().encode(data);const token = getTokenCodec().decode(dataBytes);if (token.amount > previousBalance) {const received = token.amount - previousBalance;onPayment(received);}previousBalance = token.amount;}abortController.abort();}
Zwróć uwagę, że używamy tutaj subskrypcji RPC oraz połączenia websocket z siecią Solana.
Każde powiadomienie zawiera zakodowany w base64 ciąg danych token account.
Ponieważ wiemy, że obserwowane konto to token account, możemy zdekodować te dane
za pomocą metody getTokenCodec z pakietu @solana-program/token.
W przypadku aplikacji produkcyjnych warto rozważyć bardziej zaawansowane rozwiązanie do streamingu. Przykładowe opcje to:
Parsowanie historii transakcji
Solana udostępnia metody RPC pozwalające pobrać historię transakcji konta
(getSignaturesForAddress) oraz
szczegóły transakcji (getTransaction). Aby
przeanalizować historię transakcji, pobieramy ostatnie podpisy dla naszego token
account, a następnie pobieramy pre/post saldo tokenów dla każdej transakcji.
Porównując saldo ATA przed i po każdej transakcji, możemy określić kwotę
płatności oraz jej kierunek (przychodząca vs wychodząca).
async function getRecentPayments(tokenAccountAddress: Address,limit = 100): Promise<Payment[]> {const signatures = await rpc.getSignaturesForAddress(tokenAccountAddress, { limit }).send();const payments: ParsedPayment[] = [];for (const sig of signatures) {const tx = await rpc.getTransaction(sig.signature, { maxSupportedTransactionVersion: 0 }).send();if (!tx?.meta?.preTokenBalances || !tx?.meta?.postTokenBalances) continue;// Find our ATA's index in the transactionconst accountKeys = tx.transaction.message.accountKeys;const ataIndex = accountKeys.findIndex((key) => key === ata);if (ataIndex === -1) continue;// Compare pre/post balances for our ATAconst pre = tx.meta.preTokenBalances.find((b) => b.accountIndex === ataIndex && b.mint === USDC_MINT);const post = tx.meta.postTokenBalances.find((b) => b.accountIndex === ataIndex && b.mint === USDC_MINT);const preAmount = BigInt(pre?.uiTokenAmount.amount ?? "0");const postAmount = BigInt(post?.uiTokenAmount.amount ?? "0");const diff = postAmount - preAmount;if (diff !== 0n) {payments.push({signature: sig.signature,timestamp: tx.blockTime,amount: diff > 0n ? diff : -diff,type: diff > 0n ? "incoming" : "outgoing"});}}return payments;}
Aby zidentyfikować drugą stronę transakcji, możesz przeanalizować salda tokenów w transakcji i poszukać innego konta, którego saldo zmieniło się w przeciwnym kierunku — jeśli otrzymałeś środki, znajdź konto, którego saldo zmniejszyło się o tę samą kwotę.
Ponieważ transfery tokenów SPL mogą obejmować nie tylko płatności między użytkownikami, to podejście może wykryć również transakcje, które nie są płatnościami. Dobrym rozwiązaniem alternatywnym jest użycie Memo.
Ograniczenia analizy sald przed/po transakcji
Powyższe podejście sprawdza się w przypadku prostych przepływów płatności. Jednak firmy obsługujące płatności na dużą skalę często potrzebują bardziej szczegółowych i aktualnych danych:
- Podział na instrukcje: Jedna transakcja może zawierać wiele transferów. Sald przed/po pokazują tylko zmianę końcową, a nie poszczególne transfery.
- Transakcje wielostronne: Złożone transakcje (swapy, płatności zbiorcze) obejmują wiele kont. Różnice w saldach nie pokazują pełnego przepływu środków.
- Wymogi audytowe: Zgodność finansowa często wymaga odtworzenia dokładnej sekwencji transferów, a nie tylko końcowych sald.
W przypadku systemów produkcyjnych obsługujących duże wolumeny warto rozważyć dedykowane rozwiązania indeksujące, które analizują poszczególne instrukcje transferu i dostarczają szczegółowych danych na poziomie transakcji.
Uzgadnianie płatności za pomocą Memo
Gdy nadawcy dołączają memo (np. ID faktury, numery zamówień), możesz je
wyodrębnić z wiadomości transakcji, korzystając z metody RPC getTransaction
oraz kodowania jsonParsed:
function extractMemos(transaction): string | null {const { instructions } = transaction.transaction.message;let memos = "";for (const instruction of instructions) {if (instruction.program !== "spl-memo") continue;memos += instruction.parsed + "; ";}return memos;}async function getTransactionMemo(signature: Signature): Promise<string | null> {const tx = await rpc.getTransaction(signature, {maxSupportedTransactionVersion: 0,encoding: "jsonParsed"}).send();if (!tx) return null;return extractMemos(tx);}
Zabezpieczenia
Kilka scenariuszy błędów, których należy unikać:
-
Zaufanie frontendowi. Strona płatności wyświetla komunikat „płatność zakończona” — ale czy transakcja faktycznie została zrealizowana? Zawsze weryfikuj po stronie serwera, odpytywając RPC. Potwierdzenia po stronie frontendowej mogą być sfałszowane.
-
Działanie na podstawie statusu „processed”. Transakcje Solana przechodzą przez trzy etapy: processed → confirmed → finalized. Transakcja o statusie „processed” może zostać odrzucona podczas forka. Poczekaj na status „confirmed” (1–2 sekundy) przed wysyłką zamówień lub „finalized” (~13 sekund) w przypadku transakcji o wysokiej wartości.
-
Ignorowanie mint. Każdy może stworzyć token o nazwie "USDC". Zweryfikuj, czy mint token account odpowiada prawdziwemu adresowi mint stablecoina oraz właściwemu programowi tokenów, a nie tylko nazwie tokena.
-
Podwójna realizacja. Twój webhook się uruchamia, wysyłasz zamówienie. Występuje problem z siecią, webhook uruchamia się ponownie. Teraz wysłałeś dwa razy. Przechowuj podpisy przetworzonych transakcji i sprawdzaj je przed realizacją zamówienia.
Is this page helpful?