Quét để tải ứng dụng Gate
qrCode
Thêm tùy chọn tải xuống
Không cần nhắc lại hôm nay

Phát triển hệ thống ví sàn giao dịch — Kết nối chuỗi Solana

Trong bài viết trước, chúng ta đã hoàn thiện hệ thống kiểm soát rủi ro của sàn giao dịch, còn trong bài này sẽ hướng dẫn tích hợp ví của sàn vào chuỗi Solana. Mô hình tài khoản, lưu trữ nhật ký và cơ chế xác nhận của Solana khác rất nhiều so với các chuỗi dựa trên Ethereum. Nếu cứ áp dụng theo cách của Ethereum, rất dễ gặp phải các vấn đề không mong muốn. Dưới đây chúng ta sẽ tổng hợp lại các ý tưởng chính để ghi nhận toàn diện về Solana.

Hiểu rõ đặc điểm riêng của Solana

Mô hình tài khoản của Solana

Solana sử dụng mô hình tách biệt giữa chương trình và dữ liệu, chương trình có thể dùng chung, còn dữ liệu của chương trình được lưu trữ riêng biệt qua các tài khoản PDA (Program Derived Address). Vì chương trình là dùng chung, nên cần Token Mint để phân biệt các Token khác nhau. Tài khoản Token Mint lưu trữ siêu dữ liệu toàn cục của token như quyền đúc (mint_authority), tổng cung (supply), số thập phân (decimals),
Mỗi token có một địa chỉ Mint duy nhất làm định danh, ví dụ USDC trên mạng chính của Solana có địa chỉ EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.

Trên Solana có hai bộ chương trình Token: SPL Token và SPL Token-2022. Mỗi loại Token đều có ATA (Associated Token Account) riêng để lưu trữ số dư của người dùng. Khi chuyển token, thực chất là gọi chương trình tương ứng để chuyển token giữa các tài khoản ATA.

Giới hạn nhật ký của Solana

Trên Ethereum, ta phân tích nhật ký chuyển khoản để lấy dữ liệu chuyển token. Còn với Solana, nhật ký thực thi không được lưu giữ vĩnh viễn theo mặc định, và nhật ký không thuộc trạng thái sổ cái (state) — cũng không có bộ lọc Bloom cho nhật ký — và có thể bị cắt bớt trong quá trình thực thi.
Vì vậy, không thể dựa vào “quét nhật ký” để đối chiếu nạp rút, mà phải dùng getBlock hoặc getSignaturesForAddress để phân tích lệnh.

Xác nhận và tổ chức lại khối của Solana

Thời gian tạo khối của Solana khoảng 400ms, sau 32 xác nhận (khoảng 12 giây) sẽ đạt trạng thái finalized. Nếu yêu cầu về thời gian thực không quá cao, có thể chỉ tin tưởng vào các khối đã finalized.
Để có độ chính xác cao hơn, cần xử lý khả năng xảy ra tổ chức lại khối (reorg), dù ít xảy ra. Tuy nhiên, do cơ chế đồng thuận của Solana không dựa vào parentBlockHash để tạo chuỗi, không thể dùng cách như Ethereum (so sánh parentBlockHash và blockHash trong database để phát hiện phân nhánh).
Vậy làm thế nào để xác định khối bị tổ chức lại?
Trong quá trình quét cục bộ, ta cần ghi lại blockhash của slot. Nếu phát hiện cùng slot mà blockhash thay đổi, tức là đã xảy ra rollback.

Hiểu rõ các đặc điểm của Solana, từ đó bắt đầu thực hiện, trước tiên cần chỉnh sửa cấu trúc dữ liệu trong database:

Thiết kế bảng dữ liệu

Vì Solana có hai loại Token, nên trong bảng tokens cần thêm trường token_type để phân biệt SPL Token và SPL Token-2022.

Dù địa chỉ của Solana khác Ethereum, nhưng vẫn có thể dùng BIP32, BIP44 để khai thác, chỉ khác về đường dẫn. Do đó, vẫn dùng bảng wallets cũ, nhưng để hỗ trợ ánh xạ ATA và theo dõi quét khối của Solana, cần thêm 3 bảng sau:

Tên bảng Trường chính Mô tả
solana_slots slot, block_hash, status, parent_slot Lưu trữ slot, giúp phát hiện phân nhánh và rollback
solana_transactions tx_hash, slot, to_addr, token_mint, amount, type Chi tiết giao dịch nạp/rút, tx_hash duy nhất để theo dõi hai chiều
solana_token_accounts wallet_id, wallet_address, token_mint, ata_address Lưu ánh xạ ATA của người dùng, giúp tra cứu tài khoản nội bộ qua ata_address

Trong đó:

  • solana_slots ghi nhận trạng thái confirmed/finalized/skipped, scanner dựa vào trạng thái để quyết định lưu hoặc rollback.
  • solana_transactions lưu trữ các giao dịch với đơn vị nhỏ nhất của lamports hoặc token, có trường type để phân biệt deposit/withdraw, các thao tác nhạy cảm vẫn cần ký xác thực.
  • solana_token_accounts liên kết với bảng wallets/users, đảm bảo tính duy nhất của ATA (kết hợp wallet_address + token_mint), là chỉ số chính của logic quét.

Chi tiết định nghĩa các bảng xem tại db_gateway/database.md

Xử lý nạp tiền của người dùng

Việc xử lý nạp tiền đòi hỏi quét liên tục dữ liệu trên chuỗi Solana, có hai phương pháp chính:

  1. Quét chữ ký (signatures): getSignaturesForAddress(
  2. Quét khối (block): getBlock)

Phương pháp 1: Quét chữ ký của địa chỉ, gọi getSignaturesForAddress( với tham số là địa chỉ người dùng (có thể là ATA hoặc programID). Phương thức này lấy danh sách chữ ký mới nhất của địa chỉ đó, sau đó dùng getTransaction) để lấy dữ liệu giao dịch. Phương pháp này phù hợp khi số lượng tài khoản hoặc dữ liệu ít.

Phương pháp 2: Quét khối, liên tục lấy slot mới, gọi getBlock(slot) để lấy toàn bộ dữ liệu giao dịch, sau đó phân tích các lệnh trong transaction.message.instructions và meta.innerInstructions. Phương pháp này phù hợp khi số lượng tài khoản lớn.

Lưu ý: Với lưu lượng giao dịch lớn của Solana, TPS cao, trong môi trường sản xuất có thể gặp tình trạng phân tích chậm hơn tốc độ tạo khối. Lúc này cần dùng message queue như Kafka hoặc RabbitMQ để lọc các chuyển token tiềm năng, đẩy vào hàng đợi để xử lý chính xác sau này. Một số dữ liệu nóng cần lưu trong Redis để tránh tắc nghẽn hàng đợi. Nếu số địa chỉ người dùng quá lớn, có thể phân mảnh theo ATA, nhiều consumer xử lý các phân mảnh khác nhau để tăng hiệu quả.

Ngoài ra, có thể dùng dịch vụ Indexer của các nhà cung cấp RPC thứ ba, hỗ trợ webhook, theo dõi tài khoản, lọc nâng cao, phù hợp với lượng dữ liệu lớn.

Quy trình quét khối

Chúng ta dùng phương pháp 2, mã nguồn chính nằm trong module scan/solana-scan gồm blockScanner.ts và txParser.ts, quy trình chính như sau:

1. Giai đoạn đồng bộ ban đầu, bổ sung các khối lịch sử (performInitialSync)

  • Bắt đầu từ slot cuối cùng đã quét, quét từng slot đến slot mới nhất
  • Mỗi 100 slot kiểm tra slot mới xuất hiện, cập nhật mục tiêu
  • Dùng commitment là confirmed để cân nhắc độ thực tế

2. Giai đoạn quét mới (scanNewSlots)

  • Liên tục kiểm tra slot mới
  • Xác nhận lại các slot confirmed gần nhất (phát hiện rollback)

3. Phân tích khối (txParser.parseBlock)

  • Gọi getBlock)slot( với commitment là confirmed, encoding jsonParsed
  • Duyệt từng giao dịch trong block, lấy message.instructions và meta.innerInstructions
  • Chỉ xử lý các giao dịch thành công (tx.meta.err === null)

4. Phân tích lệnh (txParser.parseInstruction)

  • Chuyển SOL: so khớp với System Program )11111…(, kiểm tra kiểu transfer, destination có trong danh sách theo dõi
  • Chuyển SPL Token: so khớp với Token Program hoặc Token-2022 Program, kiểm tra destination là ATA, sau đó qua ánh xạ trong database để lấy wallet và token mint.

Xử lý rollback:
Chương trình liên tục lấy slot đã finalized, nếu slot ≤ finalizedSlot thì đánh dấu finalized, còn các slot trong confirmed thì so sánh blockhash để phát hiện rollback.

Ví dụ mã chính:

// blockScanner.ts - quét một slot
async function 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 - phân tích lệnh chuyển khoản
for (const tx of block.transactions) {
  if (tx.meta?.err) continue; // bỏ qua giao dịch thất bại
  const instructions = [
    ...tx.transaction.message.instructions,
    ...(tx.meta.innerInstructions ?? []).flatMap(i => i.instructions)
  ];
  for (const ix of instructions) {
    // Chuyển SOL
    if (ix.programId === SYSTEM_PROGRAM_ID && ix.parsed?.type === 'transfer') {
      if (monitoredAddresses.has(ix.parsed.info.destination)) {
        // xử lý
      }
    }
    // Chuyển 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;
        const walletAddress = ataToWalletMap.get(ataAddress);
        if (walletAddress && monitoredAddresses.has(walletAddress)) {
          // xử lý
        }
      }
    }
  }
}

Sau khi quét ra các giao dịch nạp, dựa trên cơ chế an toàn của DB Gateway + ký xác thực, dữ liệu sẽ được ghi vào bảng dòng tiền (credits).

Rút tiền

Quy trình rút tiền của Solana tương tự EVM, nhưng có điểm khác:

  1. Có hai loại Token: SPL-Token và SPL-Token-2022, cần phân biệt programID khi xây dựng lệnh.
  2. Giao dịch gồm hai phần: signatures (dãy chữ ký ed25519) và message (chứa header, accountKeys, recentBlockhash, instructions). Message được hash và ký. Không có nonce, thay vào đó dùng recentBlockhash để giới hạn thời gian hiệu lực (khoảng 150 khối, ~1 phút). Mỗi lần rút, cần lấy recentBlockhash mới từ chain, rồi xây dựng giao dịch.

Quy trình rút tiền

Hình minh họa:

![image-20240930222847819.png]

Thực tế, lấy blockhash sau kiểm tra rủi ro sẽ hợp lý hơn.

Mã ký ký giao dịch:

  • Tùy theo loại giao dịch, xây dựng lệnh chuyển SOL hoặc Token
  • Tạo message transaction, ký bằng module signer
  • Gửi giao dịch đã ký qua @solana/web3.js

Ví dụ:

// Lệnh chuyển SOL
const instruction = getTransferSolInstruction({
  source: hotWalletSigner,
  destination: solanaAddress.to,
  amount: BigInt(amount)
});

// Lệnh chuyển Token
const instruction = getTransferInstruction({
  source: sourceAta,
  destination: destAta,
  authority: hotWalletSigner,
  amount: BigInt(amount)
});

// Xây dựng message
const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  tx => setTransactionMessageFeePayerSigner(hotWalletSigner, tx),
  tx => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }),
  tx => appendTransactionMessageInstruction(instruction)
);

// Ký và gửi
const signedTx = await signTransactionMessageWithSigners(transactionMessage);
const signedTransaction = getBase64EncodedWireTransaction(signedTx);

Gửi giao dịch:

const solanaRpc = chainConfigManager.getSolanaRpc();
const txSignature = await solanaRpc.sendTransaction(signedTransaction, ...);

Các mã nguồn chính nằm trong:

  • Wallet: walletBusinessService.ts:405-754
  • Signer: solanaSigner.ts:29-122
  • Test: requestWithdrawOnSolana.ts

Hai điểm cần tối ưu:

  1. Kiểm tra trước ATA: đảm bảo ATA đã tạo, nếu chưa thì tạo thêm phí
  2. Tăng phí ưu tiên khi mạng tắc nghẽn bằng computeUnitPrice

Tổng kết

Việc tích hợp Solana vào sàn không làm thay đổi kiến trúc tổng thể, chỉ cần thích nghi với mô hình tài khoản, cấu trúc giao dịch và cơ chế xác nhận của nó.
Trong xử lý nạp, cần duy trì bảng ánh xạ ATA → ví, theo dõi biến động blockhash để phát hiện reorg, cập nhật trạng thái giao dịch.
Trong rút, lấy recentBlockhash mới, phân biệt Token và Token-2022 để xây dựng giao dịch phù hợp.

SOL-2%
ETH-1.27%
Xem bản gốc
Trang này có thể chứa nội dung của bên thứ ba, được cung cấp chỉ nhằm mục đích thông tin (không phải là tuyên bố/bảo đảm) và không được coi là sự chứng thực cho quan điểm của Gate hoặc là lời khuyên về tài chính hoặc chuyên môn. Xem Tuyên bố từ chối trách nhiệm để biết chi tiết.
  • Phần thưởng
  • Bình luận
  • Đăng lại
  • Retweed
Bình luận
0/400
Không có bình luận
  • Gate Fun hotXem thêm
  • Vốn hóa:$4.18KNgười nắm giữ:2
    0.00%
  • Vốn hóa:$4.15KNgười nắm giữ:1
    0.00%
  • Vốn hóa:$4.15KNgười nắm giữ:1
    0.00%
  • Vốn hóa:$4.13KNgười nắm giữ:1
    0.00%
  • Vốn hóa:$4.12KNgười nắm giữ:1
    0.00%
  • Ghim
Giao dịch tiền điện tử mọi lúc mọi nơi
qrCode
Quét để tải xuống ứng dụng Gate
Cộng đồng
Tiếng Việt
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)