🚀 Gate Square “Gate Fun Token Challenge” is Live!
Create tokens, engage, and earn — including trading fee rebates, graduation bonuses, and a $1,000 prize pool!
Join Now 👉 https://www.gate.com/campaigns/3145
💡 How to Participate:
1️⃣ Create Tokens: One-click token launch in [Square - Post]. Promote, grow your community, and earn rewards.
2️⃣ Engage: Post, like, comment, and share in token community to earn!
📦 Rewards Overview:
Creator Graduation Bonus: 50 GT
Trading Fee Rebate: The more trades, the more you earn
Token Creator Pool: Up to $50 USDT per user + $5 USDT for the first 50 launche
Exchange wallet system development — integrating Solana blockchain
In our previous article, we completed the exchange’s risk control system. In this article, we will discuss integrating Solana into the exchange wallet. Solana’s account model, log storage, and confirmation mechanism differ significantly from Ethereum-based chains. Simply applying Ethereum routines can easily lead to pitfalls. Here, we outline the overall approach to recording Solana.
Understanding the Unique Aspects of Solana
Solana Account Model
Solana uses a program and data separation model. Programs are shareable, while program data is stored separately in PDA (Program Derived Address) accounts. Since programs are shared, Token Mint accounts are used to distinguish different tokens. Token Mint accounts store global token metadata such as mint authority, total supply, and decimals.
Each token has a unique Mint account address as an identifier. For example, USD Coin (USDC) on the Solana mainnet has the Mint address EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.
Solana has two sets of Token programs: SPL Token and SPL Token-2022. Each SPL Token has an associated ATA (Associated Token Account) to hold user balances. Token transfers involve calling their respective programs to transfer tokens between ATAs.
Log Limitations on Solana
On Ethereum, token transfers are tracked by parsing historical transfer logs. Solana’s execution logs are not permanently retained by default; they are not part of the ledger state and lack log Bloom filters. Logs may also be truncated during execution.
Therefore, we cannot rely on log scanning for deposit reconciliation. Instead, we must use getBlock or getSignaturesForAddress to parse instructions.
Confirmation and Reorganization in Solana
Solana produces blocks approximately every 400ms. After about 12 seconds (32 confirmations), a block reaches ‘finalized’ status. For low-latency requirements, trusting only finalized blocks is sufficient.
For higher real-time needs, consider potential chain reorganizations, which are rare but possible. Unlike Ethereum, Solana’s consensus does not rely on parentBlockHash to form a chain structure, so fork detection cannot be based solely on parentBlockHash and blockHash mismatches.
To detect reorgs locally, record the blockhash for each slot. If the blockhash for the same slot changes, it indicates a rollback.
Understanding these differences, we can proceed with implementation. First, consider the necessary database modifications:
Database Table Design
Since Solana has two token types, we add a token_type field to the tokens table to distinguish between SPL Token and SPL Token-2022.
Although Solana addresses differ from Ethereum, they can be derived using BIP32/BIP44 with different derivation paths. The existing wallets table can be reused. To support ATA address mapping and chain scanning, add the following tables:
Details:
Refer to db_gateway/database.md for detailed table definitions.
Processing User Deposits
Deposit processing involves continuously scanning on-chain data, typically via two methods:
Method 1: Signature scanning involves calling getSignaturesForAddress with the user’s ATA address or program ID, passing parameters like before, until, limit. This retrieves signatures related to the address, which can then be fetched via getTransaction for detailed info. Suitable for small account sets.
Method 2: Block scanning involves fetching the latest slot, then calling getBlock for each slot to get transaction details, signatures, and account info. This is more suitable for large account sets.
Note: Due to high transaction throughput and TPS on Solana, parsing and filtering may lag behind block production. Using message queues (Kafka/RabbitMQ) to push potential deposit events for downstream processing can improve efficiency. Hot data can be cached in Redis to prevent queue buildup. For many addresses, sharding by ATA addresses and multiple consumers can enhance throughput.
Alternatively, third-party RPC providers offer indexer services with webhook and account monitoring, which can handle large-scale data parsing.
Block Scanning Workflow
We use method two, with core code in scan/solana-scan modules (blockScanner.ts and txParser.ts). The main steps:
Reorg handling:
Sample core code snippets:
// blockScanner.ts - scan a single slot 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 - parse transfer instructions for (const tx of block.transactions) { if (tx.meta?.err) continue; // skip failed tx const instructions = [ …tx.transaction.message.instructions, …(tx.meta.innerInstructions ?? []).flatMap(i => i.instructions), ]; for (const ix of instructions) { // SOL transfer if (ix.programId === SYSTEM_PROGRAM_ID && ix.parsed?.type === ‘transfer’) { if (monitoredAddresses.has(ix.parsed.info.destination)) { // process deposit } } // SPL Token transfer 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; const walletAddress = ataToWalletMap.get(ataAddress); if (walletAddress && monitoredAddresses.has(walletAddress)) { // process deposit } } } } }
Deposit Processing Summary:
Withdrawal Process
Solana withdrawals are similar to EVM chains but differ in transaction construction:
Withdrawal workflow:
Sample code for signing:
// SOL transfer instruction const instruction = getTransferSolInstruction({ source: hotWalletSigner, destination: solanaAddress.to, amount: BigInt(amount), }); // Token transfer instruction const instruction = getTransferInstruction({ source: sourceAta, destination: destAta, authority: hotWalletSigner, amount: BigInt(amount), });
// Build transaction message const transactionMessage = pipe( createTransactionMessage({ version: 0 }), tx => setTransactionMessageFeePayerSigner(hotWalletSigner, tx), tx => setTransactionMessageLifetime({ blockhash, lastValidBlockHeight }), tx => appendTransactionMessageInstruction(instruction), );
// Sign transaction const signedTx = await signTransactionMessageWithSigners(transactionMessage); // Encode for sending const signedTransaction = getBase64EncodedWireTransaction(signedTx);
Wallet Module: walletBusinessService.ts:405-754
Signer Module: solanaSigner.ts:29-122
Test Scripts: requestWithdrawOnSolana.ts
Optimization notes:
Summary
Integrating Solana into the exchange does not fundamentally change the architecture but requires adapting to its unique account model, transaction structure, and consensus confirmation mechanism.
Pre-establish and maintain ATA-to-wallet mappings for token transfers, monitor blockhash changes to detect reorgs, and dynamically update transaction statuses from confirmed to finalized.
During withdrawals, fetch the latest blockhash, distinguish token types, and construct appropriate transactions accordingly.