I token arrivano nel tuo wallet nel momento in cui una transazione viene confermata. Non è richiesta alcuna azione da parte del destinatario. Solana incrementa atomicamente il saldo del token account del destinatario e decrementa il saldo del mittente. In questa guida, copriamo alcuni strumenti utili per comprendere il saldo del tuo token account e monitorare i pagamenti in entrata.
Interrogare il saldo dei token
Controlla il tuo saldo in stablecoin utilizzando il metodo 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};}
Monitorare i trasferimenti in entrata
Iscriviti al tuo token account per ricevere notifiche di pagamento in tempo
reale utilizzando il metodo 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();}
Nota che qui stiamo utilizzando le RPC Subscriptions e una connessione websocket alla rete Solana.
Ogni notifica contiene una stringa codificata in base64 dei dati del token
account. Poiché sappiamo che l'account che stiamo osservando è un token account,
possiamo decodificare i dati utilizzando il metodo getTokenCodec dal package
@solana-program/token.
Nota che per le applicazioni in produzione, dovresti considerare una soluzione di streaming più robusta. Alcune opzioni includono:
Analizzare la cronologia delle transazioni
Solana dispone di metodi RPC che ti consentono di recuperare la cronologia delle
transazioni di un account
(getSignaturesForAddress) e ottenere
i dettagli di una transazione
(getTransaction). Per analizzare la
cronologia delle transazioni, recuperiamo le firme recenti per il nostro token
account, quindi recuperiamo i saldi dei token pre/post di ciascuna transazione.
Confrontando il saldo del nostro ATA prima e dopo ogni transazione, possiamo
determinare l'importo del pagamento e la direzione (in entrata vs in uscita).
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;}
Per identificare la controparte, puoi scansionare i saldi dei token della transazione per trovare un altro account il cui saldo è cambiato nella direzione opposta—se hai ricevuto fondi, cerca un account il cui saldo è diminuito dello stesso importo.
Poiché i trasferimenti di token SPL possono esistere oltre i semplici pagamenti tra utenti, questo approccio potrebbe produrre alcune transazioni che non sono pagamenti. Una buona alternativa qui è utilizzare i memo.
Limitazioni dell'analisi dei saldi pre/post
L'approccio sopra descritto funziona bene per flussi di pagamento semplici. Tuttavia, le aziende che elaborano pagamenti su larga scala spesso necessitano di dati più granulari e in tempo reale:
- Suddivisione per istruzione: Una singola transazione può contenere più trasferimenti. I saldi pre/post mostrano solo la variazione netta, non i singoli trasferimenti.
- Transazioni multi-parte: Le transazioni complesse (swap, pagamenti batch) coinvolgono più account. Le differenze di saldo non rivelano il flusso completo dei fondi.
- Requisiti di audit: La conformità finanziaria spesso richiede la ricostruzione delle sequenze di trasferimento esatte, non solo i saldi finali.
Per i sistemi di produzione che gestiscono volumi elevati, considera soluzioni di indicizzazione dedicate che analizzano le singole istruzioni di trasferimento e forniscono dettagli a livello di transazione.
Riconciliare i pagamenti con i memo
Quando i mittenti includono memo (ID fattura, numeri d'ordine), puoi estrarli
dal messaggio della transazione utilizzando il metodo RPC getTransaction e la
codifica 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);}
Protezioni
Alcuni scenari di errore da evitare:
-
Fidarsi del frontend. Una pagina di checkout dice "pagamento completato"—ma la transazione è effettivamente arrivata? Verifica sempre lato server interrogando l'RPC. Le conferme frontend possono essere falsificate.
-
Agire sullo stato "processed". Le transazioni Solana attraversano tre fasi: processed → confirmed → finalized. Una transazione "processed" può ancora essere scartata durante i fork. Attendi "confirmed" (1-2 secondi) prima di spedire gli ordini, o "finalized" (~13 secondi) per transazioni di alto valore.
-
Ignorare il mint. Chiunque può creare un token chiamato "USDC". Verifica che il mint del token account corrisponda all'indirizzo del mint della stablecoin reale e al programma token, non solo al nome del token.
-
Doppio adempimento. Il tuo webhook si attiva, spedisci l'ordine. Interruzione di rete, il webhook si attiva di nuovo. Ora hai spedito due volte. Memorizza le firme delle transazioni elaborate e controlla prima di procedere all'adempimento.
Is this page helpful?