Release Funds

Called by a provisioned operator to release escrowed tokens to a user's Mainnet wallet, contingent on a valid Sparse Merkle Tree exclusion proof for the withdrawal nonce.

The escrow program is migrating from this Sparse Merkle Tree design to an on-chain nonce bitmap (tracked upstream). This page is accurate against the current escrow program and will be reworked once that change lands.

Accounts

AccountWritableSignerDescription
payer✓✓Fee payer
operator✓Operator signer
instance✓Escrow instance PDA (root updated on-chain)
operator_pdaOperator PDA proving the signer is provisioned
mintSPL token mint
allowed_mintAllowedMint PDA - validates the mint is permitted on this instance
user_ata✓Recipient's associated token account (destination)
instance_ata✓Escrow's associated token account (source)
token_programSPL Token Program
associated_token_programAssociated Token Program
event_authorityEvent authority PDA for EmitEvent CPI
private_channel_escrow_programEscrow Program (self-reference for CPI)

Parameters

ParameterTypeDescription
amountu64Amount of tokens to release
userPubkeyRecipient wallet on Mainnet
new_withdrawal_root[u8; 32]Updated SMT root after including this withdrawal's nonce
transaction_nonceu64Unique nonce identifying this withdrawal leaf in the SMT
sibling_proofs[u8; 512]16 sibling hashes (32 bytes each) for the SMT exclusion proof

Returns

Returns a TransactionBuilder that can be used to build and send the transaction.

Example

import { getReleaseFundsInstruction } from "../private-channel-escrow-program/clients/typescript/src/generated";
const releaseIx = getReleaseFundsInstruction({
payer: operatorPayerSigner,
operator: operatorSigner,
instance: instancePda,
operatorPda: operatorPdaAddress,
mint: mintAddress,
allowedMint: allowedMintPda,
userAta: userAtaAddress,
instanceAta: instanceAtaAddress,
tokenProgram: TOKEN_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
eventAuthority: eventAuthorityPda,
privateChannelEscrowProgram: ESCROW_PROGRAM_ID,
amount: 1_000_000n,
user: recipientPublicKey,
newWithdrawalRoot: newSmtRoot, // [u8; 32]
transactionNonce: withdrawalNonce, // u64
siblingProofs: proofBytes // Uint8Array(512)
});

Important Notes

  • Caller must be a provisioned operator: operator_pda must exist (created by AddOperator); invalid PDA throws InvalidOperatorPda
  • sibling_proofs is exactly 512 bytes (16 × 32-byte hashes, matching the SMT tree height of 16)
  • new_withdrawal_root is the updated SMT root after including this withdrawal's nonce. Compute it off-chain before calling
  • transaction_nonce must be valid for Instance.current_tree_index; an invalid nonce throws InvalidTransactionNonceForCurrentTreeIndex
  • The program first runs verify_smt_exclusion_proof, proving the nonce does NOT yet exist against the current root, then runs a separate verify_smt_inclusion_proof proving the nonce DOES exist in the caller-supplied new_withdrawal_root. Either check failing throws InvalidSmtProof
  • Only after both checks pass is Instance.withdrawal_transactions_root updated atomically; the nonce cannot be reused
  • Emits an event via EmitEvent CPI

Is this page helpful?

Table of Contents

Edit Page
© 2026 Solana Foundation. All rights reserved.