Token được chuyển vào ví của bạn ngay khi giao dịch được xác nhận. Người nhận không cần thực hiện bất kỳ hành động nào. Solana tự động tăng số dư token account của người nhận và giảm số dư của người gửi. Trong hướng dẫn này, chúng tôi giới thiệu một số công cụ hữu ích để hiểu số dư token account của bạn và theo dõi các khoản thanh toán đến.
Truy vấn số dư token
Kiểm tra số dư stablecoin của bạn bằng phương thức 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};}
Theo dõi chuyển khoản đến
Đăng ký token account của bạn để nhận thông báo thanh toán theo thời gian thực
bằng phương thức 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();}
Lưu ý rằng chúng ta đang sử dụng RPC Subscriptions và kết nối websocket tới mạng Solana.
Mỗi thông báo chứa một chuỗi mã hóa base64 của dữ liệu token account. Vì chúng
ta biết tài khoản đang xem là token account, chúng ta có thể giải mã dữ liệu
bằng phương thức getTokenCodec từ gói @solana-program/token.
Lưu ý rằng đối với các ứng dụng production, bạn nên cân nhắc một giải pháp streaming mạnh mẽ hơn. Một số lựa chọn bao gồm:
Phân tích lịch sử giao dịch
Solana có các phương thức RPC cho phép bạn lấy lịch sử giao dịch của tài khoản
(getSignaturesForAddress) và lấy chi
tiết của giao dịch (getTransaction). Để phân
tích lịch sử giao dịch, chúng ta lấy các chữ ký gần đây cho token account của
mình, sau đó truy xuất số dư token trước/sau của mỗi giao dịch. Bằng cách so
sánh số dư ATA của chúng ta trước và sau mỗi giao dịch, chúng ta có thể xác định
số tiền thanh toán và hướng (đến hoặc đi).
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;}
Để xác định bên giao dịch, bạn có thể quét số dư token của giao dịch để tìm tài khoản khác có số dư thay đổi theo hướng ngược lại—nếu bạn nhận được tiền, hãy tìm tài khoản có số dư giảm đi cùng một số tiền.
Vì các giao dịch chuyển token SPL có thể tồn tại ngoài việc thanh toán giữa người dùng, cách tiếp cận này có thể tạo ra một số giao dịch không phải là thanh toán. Một giải pháp thay thế tốt ở đây là sử dụng Memos.
Hạn chế của phân tích số dư trước/sau
Cách tiếp cận trên hoạt động tốt cho các luồng thanh toán đơn giản. Tuy nhiên, các công ty xử lý thanh toán ở quy mô lớn thường cần dữ liệu chi tiết và thời gian thực hơn:
- Phân tích theo từng lệnh: Một giao dịch có thể chứa nhiều lần chuyển tiền. Số dư trước/sau chỉ hiển thị thay đổi ròng, không phải các lần chuyển riêng lẻ.
- Giao dịch nhiều bên: Các giao dịch phức tạp (hoán đổi, thanh toán hàng loạt) liên quan đến nhiều tài khoản. Chênh lệch số dư không tiết lộ luồng tiền hoàn chỉnh.
- Yêu cầu kiểm toán: Tuân thủ tài chính thường yêu cầu tái tạo chuỗi chuyển tiền chính xác, không chỉ số dư cuối cùng.
Đối với các hệ thống sản xuất xử lý khối lượng lớn, hãy xem xét các giải pháp lập chỉ mục chuyên dụng phân tích các lệnh chuyển riêng lẻ và cung cấp chi tiết cấp giao dịch.
Đối chiếu thanh toán với Memos
Khi người gửi bao gồm memos (ID hóa đơn, số đơn hàng), bạn có thể trích xuất
chúng từ thông điệp của giao dịch bằng phương thức RPC getTransaction và mã
hóa 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);}
Bảo vệ
Một số chế độ lỗi cần tránh:
-
Tin tưởng frontend. Trang thanh toán hiển thị "thanh toán hoàn tất"—nhưng giao dịch có thực sự được ghi nhận không? Luôn xác minh phía máy chủ bằng cách truy vấn RPC. Xác nhận frontend có thể bị giả mạo.
-
Hành động dựa trên trạng thái "processed". Các giao dịch Solana trải qua ba giai đoạn: processed → confirmed → finalized. Một giao dịch "processed" vẫn có thể bị loại bỏ trong quá trình fork. Đợi "confirmed" (1-2 giây) trước khi giao hàng, hoặc "finalized" (~13 giây) cho các giao dịch giá trị cao.
-
Bỏ qua mint. Bất kỳ ai cũng có thể tạo một token có tên "USDC". Hãy xác thực rằng mint của token account khớp với địa chỉ mint stablecoin thực và token program, không chỉ tên token.
-
Thực hiện hai lần. Webhook của bạn kích hoạt, bạn giao hàng. Mạng bị gián đoạn, webhook kích hoạt lại. Bây giờ bạn đã giao hai lần. Hãy lưu trữ các chữ ký giao dịch đã xử lý và kiểm tra trước khi thực hiện.
Is this page helpful?