提取资金

概述

从 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 RPC
const 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 后无需进一步操作。操作员服务会自动处理结算:

  1. indexer-private-channel 每隔 1 秒轮询一次通道以检测销毁事件,并在检测到您的事件时写入一条待处理提款记录
  2. 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:该叶节点的唯一 nonce
  • sibling_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 gateway
const 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?

Table of Contents

Edit Page
©️ 2026 Solana 基金会版权所有