Narzędzia weryfikacyjne

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 address
const [ata] = await findAssociatedTokenPda({
mint: USDC_MINT,
owner: walletAddress,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
// Query balance via RPC
const { value } = await rpc.getTokenAccountBalance(ata).send();
return {
raw: BigInt(value.amount), // "1000000" -> 1000000n
formatted: 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 transaction
const accountKeys = tx.transaction.message.accountKeys;
const ataIndex = accountKeys.findIndex((key) => key === ata);
if (ataIndex === -1) continue;
// Compare pre/post balances for our ATA
const 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?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco