Skip to main content

Withdraw SOL

Withdraw SOL from your private balance to any recipient address.
const result = await client.withdraw({
  lamports: number,
  recipientAddress?: string,
  referrer?: string
})

Parameters

ParameterTypeRequiredDescription
lamportsnumberYesAmount in lamports to withdraw
recipientAddressstringNoRecipient wallet address. Defaults to your own wallet
referrerstringNoOptional referrer wallet address

Returns

{
  tx: string,              // Transaction signature
  recipient: string,       // Recipient address
  amount_in_lamports: number,  // Amount received (after fees)
  fee_in_lamports: number,     // Fee paid
  isPartial: boolean       // True if balance was insufficient for full amount
}

Example

import { PrivacyCash } from 'privacycash'

const client = new PrivacyCash({
  RPC_url: process.env.SOLANA_RPC_URL!,
  owner: process.env.PRIVATE_KEY!
})

// Withdraw 0.1 SOL to a clean wallet
const result = await client.withdraw({
  lamports: 0.1 * 1_000_000_000,
  recipientAddress: 'CLEAN_WALLET_ADDRESS'
})

console.log('Transaction:', result.tx)
console.log('Amount received:', result.amount_in_lamports / 1_000_000_000, 'SOL')
console.log('Fee paid:', result.fee_in_lamports / 1_000_000_000, 'SOL')

Withdrawal Fees

ComponentAmount
Base fee0.006 SOL per recipient
Protocol fee0.35% of withdrawal amount

Fee Calculation Example

// Withdrawing 1 SOL
const withdrawAmount = 1_000_000_000 // 1 SOL in lamports

// Fee calculation:
// Base: 0.006 SOL = 6,000,000 lamports
// Protocol: 1 SOL × 0.35% = 0.0035 SOL = 3,500,000 lamports
// Total fee: ~9,500,000 lamports (~0.0095 SOL)

const result = await client.withdraw({
  lamports: withdrawAmount,
  recipientAddress: 'RECIPIENT'
})

console.log('Requested:', withdrawAmount / 1e9, 'SOL')
console.log('Received:', result.amount_in_lamports / 1e9, 'SOL')
console.log('Fee:', result.fee_in_lamports / 1e9, 'SOL')

How Withdrawals Work

1

UTXO Selection

The SDK selects your largest UTXOs to cover the withdrawal amount
2

ZK Proof Generation

A zero-knowledge proof is generated proving you own the funds
3

Relayer Submission

The proof is sent to the relayer, which signs and submits the transaction
4

Funds Received

The recipient receives funds with no on-chain link to your wallet

Privacy Guarantee

The withdrawal transaction on-chain (visible on SolScan or other explorers) contains no information about the original depositor. The zero-knowledge proof ensures:
  • The relayer cannot modify the recipient address
  • The relayer cannot modify the amount
  • Any tampering causes the transaction to fail

Partial Withdrawals

If your private balance is less than the requested amount, the SDK performs a partial withdrawal:
// You have 0.05 SOL private balance
// Request to withdraw 0.1 SOL

const result = await client.withdraw({
  lamports: 100_000_000, // 0.1 SOL
  recipientAddress: 'RECIPIENT'
})

if (result.isPartial) {
  console.log('Partial withdrawal - balance was insufficient')
  console.log('Actually withdrew:', result.amount_in_lamports / 1e9, 'SOL')
}

Withdraw to Self

If you omit recipientAddress, funds are withdrawn to your own wallet:
// Withdraw to your own wallet (unshield)
const result = await client.withdraw({
  lamports: 50_000_000 // 0.05 SOL
})

console.log('Withdrawn to:', result.recipient) // Your wallet address
Withdrawing to your own wallet reduces privacy since it links your deposit and withdrawal addresses.

Best Practices

Use Clean Wallets

Always withdraw to a fresh, never-used wallet address

Wait Before Withdrawing

Wait at least a day between deposit and withdrawal

Split Withdrawals

Split large amounts into multiple smaller withdrawals over time

Vary Amounts

Don’t withdraw the exact same amount you deposited

Example: Privacy-Optimized Withdrawal

// Deposited 1 SOL yesterday

// Good: Withdraw different amounts over multiple days
await client.withdraw({
  lamports: 400_000_000, // 0.4 SOL
  recipientAddress: 'CLEAN_WALLET_1'
})

// Wait a day...

await client.withdraw({
  lamports: 350_000_000, // 0.35 SOL
  recipientAddress: 'CLEAN_WALLET_2'
})

// Wait another day...

await client.withdraw({
  lamports: 200_000_000, // 0.2 SOL
  recipientAddress: 'CLEAN_WALLET_3'
})

Error Handling

try {
  const result = await client.withdraw({
    lamports: 100_000_000,
    recipientAddress: 'RECIPIENT'
  })
  console.log('Withdrawal successful:', result.tx)
} catch (error) {
  if (error.message.includes('no balance')) {
    console.error('No private balance available')
  } else if (error.message.includes('Need at least 1 unspent UTXO')) {
    console.error('No UTXOs available for withdrawal')
  } else {
    console.error('Withdrawal failed:', error.message)
  }
}

Common Errors

ErrorSolution
no balanceDeposit funds first
Need at least 1 unspent UTXOWait for pending deposits to confirm
withdraw amount too lowIncrease withdrawal amount to cover fees