概述
从 Private Channels 提取资金是一个两步流程。首先,用户通过在 Withdraw Program 上调用 WithdrawFunds 来销毁其通道侧的代币余额。其次,操作员在 Escrow Program(本演练中为 devnet)上调用 ReleaseFunds,并提供有效的 Sparse Merkle Tree 排除证明以释放资金。SMT 证明确保每个提款 nonce 只能使用一次,从而防止双重支付。
步骤 1:在通道上发起提款
在 Withdraw Program 上调用 WithdrawFunds 以销毁您的通道侧余额。
使用 Async 变体,它会自动派生 tokenAccount PDA,是生产环境中的推荐形式:
import { getWithdrawFundsInstructionAsync } from "../private-channel-withdraw-program/clients/typescript/src/generated";import {createSolanaRpc,address,pipe,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstruction,signAndSendTransactionMessageWithSigners,assertIsTransactionMessageWithSingleSendingSigner,getBase58Decoder} from "@solana/kit";// Point to the gateway, not a public Solana RPCconst privateChannelRpc = createSolanaRpc("http://localhost:8899");const withdrawIx = await getWithdrawFundsInstructionAsync({user: userSigner,mint: address(mintAddress),amount: 1_000_000n, // 1 USDC (6 decimals)destination: null // null = release to signer's devnet wallet});const { value: latestBlockhash } = await privateChannelRpc.getLatestBlockhash({ commitment: "confirmed" }).send();// Sent to the Private Channels gateway (not Solana RPC directly), which// expects the legacy transaction format - do not switch this to version 0.const transactionMessage = pipe(createTransactionMessage({ version: "legacy" }),(m) => setTransactionMessageFeePayerSigner(userSigner, m),(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),(m) => appendTransactionMessageInstruction(withdrawIx, m));assertIsTransactionMessageWithSingleSendingSigner(transactionMessage);const signatureBytes =await signAndSendTransactionMessageWithSigners(transactionMessage);const signature = getBase58Decoder().decode(signatureBytes);console.log("Withdrawal initiated:", signature);
- Withdraw Program ID:
J231K9UEpS4y4KAPwGc4gsMNCjKFRMYcQBcjVW7vBhVi - 将此交易提交至网关(
http://localhost:8899),而非公共 Solana RPC - 如果提供了
destination,释放的资金将转至该钱包,而非签名者的钱包
预期结果
调用 WithdrawFunds 后无需进一步操作。操作员服务会自动处理结算:
indexer-private-channel每隔 1 秒轮询一次通道以检测销毁事件,并在检测到您的事件时写入一条待处理提款记录operator-private-channel每隔 1 秒轮询一次数据库以查找待处理记录,并向 Solana devnet 上的 Escrow Program 提交带有所需 SMT 排除证明的ReleaseFunds;在重试之前,它会以 400 毫秒为间隔最多轮询 5 次以确认 devnet 交易
在正常情况下,资金通常会在几秒钟内出现在您的 devnet 钱包中。
操作员如何结算(参考)
通道侧销毁完成后,已配置的操作员必须在 Escrow Program 上调用 ReleaseFunds,并提供有效的 SMT 排除证明;若无法证明提款 nonce 此前未被使用,操作员将无法释放资金。此步骤由 operator-private-channel 自动处理,您无需自行调用。
操作员需提供:
amount:必须与已销毁的金额相匹配user:devnet 上的接收钱包new_withdrawal_root:本次提款后更新的 SMT 根transaction_nonce:该叶节点的唯一 noncesibling_proofs:512 字节(树证明的 16 个 32 字节兄弟节点哈希)
验证
操作员调用确认后,检查您的 devnet 代币余额:
spl-token balance <MINT_ADDRESS> --owner <YOUR_WALLET>
或通过 RPC 查询:
import { createSolanaRpc, address } from "@solana/kit";import { findAssociatedTokenPda } from "@solana-program/token";const TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");// Query devnet directly; ReleaseFunds settles here, not through the gatewayconst rpc = createSolanaRpc("https://api.devnet.solana.com");const [yourDevnetAta] = await findAssociatedTokenPda({mint: address(mintAddress),owner: address(userSigner.address),tokenProgram: TOKEN_PROGRAM_ADDRESS});const balance = await rpc.getTokenAccountBalance(yourDevnetAta).send();console.log(balance.value.uiAmountString);
您已完成快速入门
您已在 devnet 上完成了完整的 Private Channels 周期:将 SPL 代币存入托管账户、通过网关发送链下转账,并将资金提取回您的 devnet 钱包。
Is this page helpful?