Skip to content
Launch App >

You can withdraw tokens and swap them to a different token in a single transaction using Jupiter integration. This adds another layer of privacy since the output token differs from the input.

  1. Get Jupiter Quote

    First, get a swap quote from Jupiter:

    interface JupiterQuote {
    inputMint: string;
    outputMint: string;
    inAmount: string;
    outAmount: string;
    priceImpactPct: number;
    slippageBps: number;
    }
    async function getJupiterQuote(
    inputMint: string,
    outputMint: string,
    amount: number,
    slippageBps: number = 50 // 0.5%
    ): Promise<JupiterQuote> {
    // Option 1: Use Turbine.cash Jupiter proxy
    const response = await fetch(`${API_BASE}/jupiter`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
    inputMint,
    outputMint,
    amount,
    slippageBps,
    dexes: [], // Empty for all DEXes
    }),
    });
    return response.json();
    }

    Or use Jupiter API directly:

    async function getJupiterQuoteDirect(
    inputMint: string,
    outputMint: string,
    amount: number,
    slippageBps: number = 50
    ): Promise<any> {
    const params = new URLSearchParams({
    inputMint,
    outputMint,
    amount: amount.toString(),
    slippageBps: slippageBps.toString(),
    });
    const response = await fetch(
    `https://quote-api.jup.ag/v6/quote?${params}`
    );
    return response.json();
    }
  2. Generate Withdrawal Proof

    Same as regular withdrawal:

    const { proof, nullifierHash } = await generateWithdrawProof(
    deposit.nullifier,
    deposit.secret,
    merkleProof,
    merkleRoot,
    recipient,
    deposit.leafIndex
    );
  3. Call relay_swap Endpoint

    interface RelaySwapParams {
    nullifierHash: number[];
    proof: number[];
    root: number[];
    recipient: string;
    depth: number;
    depositSize: number;
    input_mint: string; // Token being withdrawn
    output_mint: string; // Token to receive after swap
    network?: string;
    }
    async function relaySwap(params: RelaySwapParams): Promise<RelayResponse> {
    const response = await fetch(`${API_BASE}/relay_swap`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(params),
    });
    return response.json();
    }
async function withdrawWithSwap(
deposit: DepositSecrets,
recipient: string,
outputMint: string,
authToken: string,
slippageBps: number = 50,
network: string = 'mainnet'
): Promise<WithdrawSwapResult> {
// 1. Get quote to preview swap
const quote = await getJupiterQuote(
deposit.mint,
outputMint,
deposit.amount,
slippageBps
);
console.log(`Swapping ${deposit.amount} for ~${quote.outAmount}`);
console.log(`Price impact: ${quote.priceImpactPct}%`);
// 2. Get Merkle proof
const proofPath = await getMerkleProof(deposit.mint, deposit.leafIndex, network);
const merkleRoot = await getMerkleRoot(deposit.mint, network);
// 3. Generate ZK proof
const { proof, nullifierHash } = await generateWithdrawProof(
deposit.nullifier,
deposit.secret,
proofPath,
merkleRoot,
recipient,
deposit.leafIndex
);
// 4. Check OFAC
await getOfacSignature(authToken, network);
// 5. Call relay_swap
const result = await relaySwap({
nullifierHash: Array.from(nullifierHash),
proof: Array.from(proof),
root: Array.from(merkleRoot),
recipient,
depth: 32,
depositSize: deposit.amount,
input_mint: deposit.mint,
output_mint: outputMint,
network,
});
if (result.status !== 200 || !result.signature) {
throw new Error(result.error || 'Swap withdrawal failed');
}
markDepositSpent(deposit);
return {
signature: result.signature,
inputAmount: deposit.amount,
expectedOutput: quote.outAmount,
};
}

Jupiter supports thousands of tokens. Common pairs:

InputOutputUse Case
USDCSOLConvert stables to native
USDCUSDTStablecoin swap
SOLUSDCDe-risk to stables

Slippage tolerance is set in basis points (bps):

SlippagebpsRisk
0.1%10May fail on volatile pairs
0.5%50Balanced (recommended)
1.0%100Higher tolerance for volatile tokens
3.0%300Use for very illiquid tokens
// Conservative slippage for stablecoins
const stablecoinSlippage = 10; // 0.1%
// Standard slippage
const defaultSlippage = 50; // 0.5%
// High slippage for volatile pairs
const volatileSlippage = 100; // 1%

Withdraw+swap includes multiple fees:

Fee TypeDescription
Relay Fee[PLACEHOLDER: FEE_WITHDRAW_PCT]
Swap Fee[PLACEHOLDER: FEE_SWAP_PCT]
Jupiter FeesDEX fees (varies by route)

Total cost = Relay Fee + Swap Fee + Jupiter routing fees

ErrorCauseSolution
SlippageExceededPrice moved too muchIncrease slippage or retry
InsufficientLiquidityNot enough liquidityTry different route/DEX
InvalidOutputMintToken not supportedCheck token address
SwapFailedJupiter swap failedCheck Jupiter status

Before submitting, validate the swap:

async function validateSwap(
inputMint: string,
outputMint: string,
amount: number
): Promise<{ valid: boolean; reason?: string }> {
try {
const quote = await getJupiterQuote(inputMint, outputMint, amount);
// Check price impact
if (quote.priceImpactPct > 3) {
return {
valid: false,
reason: `High price impact: ${quote.priceImpactPct}%`,
};
}
// Check minimum output
const minOutput = amount * 0.95; // Example: expect at least 95%
if (parseInt(quote.outAmount) < minOutput) {
return {
valid: false,
reason: 'Output amount too low',
};
}
return { valid: true };
} catch (e) {
return { valid: false, reason: 'Quote failed' };
}
}