💥 Gate 廣場活動:#发帖赢代币TRUST 💥
在 Gate 廣場發布與 TRUST 或 CandyDrop 活動相關的原創內容,即有機會瓜分 13,333 枚 TRUST 獎勵!
📅 活動時間:2025年11月6日 – 11月16日 24:00(UTC+8)
📌 相關詳情:
CandyDrop 👉 https://www.gate.com/zh/announcements/article/47990
📌 參與方式:
1️⃣ 在 Gate 廣場發布原創內容,主題需與 TRUST 或 CandyDrop 活動相關;
2️⃣ 內容不少於 80 字;
3️⃣ 貼文添加話題: #发帖赢代币TRUST
4️⃣ 附上任意 CandyDrop 活動參與截圖。
🏆 獎勵設定(總獎池:13,333 TRUST)
🥇 一等獎(1名):3,833 TRUST / 人
🥈 二等獎(3名):1,500 TRUST / 人
🥉 三等獎(10名):500 TRUST / 人
📄 注意事項:
內容必須原創,禁止抄襲或灌水;
得獎者需完成 Gate 廣場身份認證;
活動最終解釋權歸 Gate 所有。
交易所錢包系統開發——接入 Solana 鏈
上一篇我們補齊了交易所的風控體系,這一篇將介紹如何接入 Solana 鏈的錢包。Solana 的帳戶模型、日誌存儲和確認機制與以太坊系鏈有很大不同,如果沿用以太坊的套路,容易踩坑。以下我們梳理一下記錄 Solana 的整體思路。
了解獨特的 Solana
Solana 帳戶模型
Solana 採用程序與數據分離的模型,程序是可以共用的,而程序的數據則通過 PDA(Program Derived Address)帳戶單獨保存。由於程序是共用的,因此需要 Token Mint 來區分不同的 Token。Token Mint 帳戶存儲代幣的全局元數據,例如 鑄造權限(mint_authority)、總供應量(supply)、小數位數(decimals) 等。
每個代幣都有唯一的 Mint 帳戶地址作為標識符,例如 USD Coin(USDC)在 Solana 主網的 Mint 地址是 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v。
在 Solana 上有兩套 Token 程序,一是 SPL Token,另一是 SPL Token-2022。每種 SPL Token 都有獨立的 ATA(Associated Token Account)來保存用戶的餘額。在 Token 轉賬時,實際上是調用各自的程序在 Token 在 ATA 帳戶之間轉移。
Solana 日誌限制
在以太坊上,是通過解析歷史的轉賬日誌來獲取 Token 轉賬信息,但 Solana 的執行日誌預設不會永久保留,且不屬於帳本狀態(也沒有日誌的布隆過濾器),在執行過程中可能會被截斷。
因此,我們不能通過“掃描日誌”來做充值對帳,而是要使用 getBlock 或 getSignaturesForAddress 來解析指令。
Solana 確認與重組
Solana 出塊時間約為 400ms,經過 32 個確認(約 12 秒)即可達到 finalized 狀態。如果對實時性要求不高,可以只信任 finalized 的區塊。
若需更高的實時性,則需考慮可能出現的區塊重組,雖然較少發生。但由於 Solana 共識不依賴 parentBlockHash 形成鏈結構,不能像以太坊那樣通過 parentBlockHash 和資料庫中的 blockHash 不一致來判斷分叉。那應該用什麼方法來判斷區塊被重組呢?
在本地掃塊時,我們需要記錄 slot 的 blockhash,如果同一 slot 的 blockhash 發生變化,就表示發生了回滾。
理解 Solana 的不同,接下來就可以著手實現了,先看看資料庫需要做哪些修改:
資料庫表設計
由於 Solana 有兩種類型的 Token,因此,我們需要在 tokens 表中添加一個 token_type,用來區分 spl-token 和 spl-token-2022。
儘管 Solana 地址與以太坊不同,但同樣可以通過 BIP32、BIP44 衍生,只是衍生路徑不同,因此只需使用原有的 wallets 表,但為了支持 ATA 地址映射和 Solana 掃塊追蹤,需要新增以下三張表:
其中:
詳細表定義可參考 db_gateway/database.md
處理用戶充值
處理用戶充值,需要不斷掃描 Solana 鏈上數據,通常有兩種方法:
方法一:掃描地址的簽名,調用 getSignaturesForAddress(address, { before, until, limit }),傳入我們關注的地址(即為用戶生成的 ATA 地址,也可以是 programID)。注意 spl-token 的轉賬指令調用不包含 mint 地址,通過控制 before、until 參數不斷拉取增量簽名,再用 getTransaction 獲取交易信息。
此方法適合數據量或帳號較少的情況,若帳戶數非常大,則使用掃塊更合適,我們這裡採用掃塊方法。
方法二:掃描最新的 Slot,調用 getBlock(slot),獲取完整交易詳情、簽名或帳戶,然後根據指令與帳戶篩選出所需數據。
如果不想自己掃塊,還可以使用第三方 RPC 服務商提供的 Indexer 服務,例如 Webhook、帳戶監聽和高階過濾支持,能承擔大數據解析壓力。
掃塊流程
我們採用方法二,相關代碼位於 scan/solana-scan 模塊下的 blockScanner.ts 和 txParser.ts,主要流程如下:
1. 初始同步階段、補充歷史區塊(performInitialSync)
2. 掃描階段(scanNewSlots)
3. 區塊解析(txParser.parseBlock)
4. 指令解析(txParser.parseInstruction)
回滾處理:
程序會不斷獲取 finalizedSlot,若 slot ≤ finalizedSlot 即標記為 finalized。對於仍在 confirmed 狀態的區塊,通過比對 blockhash 是否變更來判斷是否回滾。
示例核心代碼如下:
// blockScanner.ts - 掃描單個槽位
async scanSingleSlot(slot: number) {
const block = await solanaClient.getBlock(slot);
if (!block) {
await insertSlot({ slot, status: ‘skipped’ });
return;
}
const finalizedSlot = await getCachedFinalizedSlot();
const status = slot <= finalizedSlot ? ‘finalized’ : ‘confirmed’;
await processBlock(slot, block, status);
}
// txParser.ts - 解析轉賬指令
for (const tx of block.transactions) {
if (tx.meta?.err) continue; // 跳過失敗交易
const instructions = [
…tx.transaction.message.instructions,
…(tx.meta.innerInstructions ?? []).flatMap(i => i.instructions)
];
for (const ix of instructions) {
// SOL 轉賬
if (ix.programId === SYSTEM_PROGRAM_ID && ix.parsed?.type === ‘transfer’) {
if (monitoredAddresses.has(ix.parsed.info.destination)) {
// …
}
}
// Token 轉賬
if (ix.programId === TOKEN_PROGRAM_ID || ix.programId === TOKEN_2022_PROGRAM_ID) {
if (ix.parsed?.type === ‘transfer’ || ix.parsed?.type === ‘transferChecked’) {
const ataAddress = ix.parsed.info.destination; // ATA 地址
const walletAddress = ataToWalletMap.get(ataAddress); // 映射到錢包地址
if (walletAddress && monitoredAddresses.has(walletAddress)) {
// …
}
}
}
}
在掃描到充值交易後,依循 DB Gateway + 風控雙簽的安全措施,驗證後將數據寫入資金流水表 credits。
提現
Solana 的提現流程與 EVM 鏈類似,但在交易構建上有所不同:
提現流程
![提現流程圖]
實際上,將獲取交易的 Blockhash 放在風控檢查之後會更合理。
Signer 模塊簽名交易核心代碼如下:
根據交易類型構建不同指令:
// SOL 轉賬指令
const instruction = getTransferSolInstruction({
source: hotWalletSigner,
destination: solanaAddress.to,
amount: BigInt(amount)
});
// Token 轉賬指令
const instruction = getTransferInstruction({
source: sourceAta,
destination: destAta,
authority: hotWalletSigner,
amount: BigInt(amount)
});
構建並簽名交易消息:
// 使用 @solana/kit 構建交易
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(hotWalletSigner, tx),
tx => setTransactionMessageLifetime({ blockhash, lastValidBlockHeight }),
tx => appendTransactionMessageInstruction(instruction)
);
// 簽名交易
const signedTx = await signTransactionMessageWithSigners(transactionMessage);
// 返回兩種編碼:
// 1. Base64 編碼的完整交易(用於發送到網絡)
const signedTransaction = getBase64EncodedWireTransaction(signedTx);
錢包模塊將交易發送到網絡
// 使用 @solana/web3.js 發送交易
const solanaRpc = chainConfigManager.getSolanaRpc();
const txSignature = await solanaRpc.sendTransaction(signedTransaction, …);
完整的提現實現代碼位於:
注意這裡有兩個待實現的優化點:
總結
接入 Solana 鏈在整體架構上沒有變化,關鍵在於適配其獨特的帳戶模型、交易結構以及共識確認機制。
在處理充值時,預先建立並維護 ATA 與錢包地址的映射表,用於 Token 轉賬識別;統一監控 blockhash 變化以檢測區塊重組,動態更新交易狀態(confirmed → finalized)。
在提現時,使用 getLatestBlockhash 獲取交易參數,同時區分 Sol、SPL Token 和 Token-2022,構造不同的交易。