개요
Private Channels에서 출금하는 과정은 두 단계로 이루어집니다. 먼저, 사용자는 Withdraw Program의 WithdrawFunds를 호출하여 채널 측 토큰 잔액을 소각합니다. 그다음, 운영자가 Escrow Program(이 가이드에서는 devnet)의 ReleaseFunds를 유효한 Sparse Merkle Tree 배제 증명과 함께 호출하여 자금을 해제합니다. SMT 증명은 각 출금 논스가 한 번만 사용될 수 있도록 보장하여 이중 지출을 방지합니다.
1단계: 채널에서 출금 시작
Withdraw Program의 WithdrawFunds를 호출하여 채널 측 잔액을 소각합니다.
tokenAccount PDA를 자동으로 도출하는 Async 변형을 사용하세요. 이 방식은
프로덕션 환경에서 권장되는 형태입니다:
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 - 이 트랜잭션은 공개 Solana RPC가 아닌 게이트웨이(
http://localhost:8899)에 제출하세요 destination이 제공된 경우, 해제된 자금은 서명자의 지갑 대신 해당 지갑으로 전송됩니다
예상 동작
WithdrawFunds 호출 후 추가 작업은 필요하지 않습니다. 운영자 서비스가
자동으로 정산을 처리합니다:
indexer-private-channel은 소각 이벤트를 감지하기 위해 매 1초마다 채널을 폴링하며, 해당 이벤트가 감지되면 대기 중인 출금 레코드를 기록합니다operator-private-channel은 매 1초마다 데이터베이스에서 대기 중인 레코드를 폴링하고, 필요한 SMT 배제 증명과 함께 Solana devnet의 Escrow Program에ReleaseFunds를 제출합니다. 재시도 전에 400ms 간격으로 최대 5회 devnet 확인을 폴링합니다
정상적인 조건에서는 일반적으로 몇 초 내에 devnet 지갑에 자금이 나타납니다.
운영자 정산 방식 (참고)
채널 측 소각 후, 프로비저닝된 운영자는 유효한 SMT 배제 증명과 함께 Escrow
Program의 ReleaseFunds를 호출해야 합니다. 운영자는 출금 논스가 이전에
사용된 적이 없음을 증명하지 않고는 자금을 해제할 수 없습니다. 이 단계는
operator-private-channel이 자동으로 처리하므로 직접 호출할 필요가 없습니다.
운영자가 제공하는 항목:
amount: 소각된 금액과 일치해야 함user: devnet의 수신자 지갑new_withdrawal_root: 이 출금 이후 업데이트된 SMT 루트transaction_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 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?