Skip to content
Launch App >

Core endpoints for private withdrawals via the relayer.

Submit a private withdrawal.

POST /relay
Content-Type: application/json
{
"nullifierHash": [1, 2, 3, ...],
"proof": [1, 2, 3, ...],
"root": [1, 2, 3, ...],
"recipient": "RecipientWalletAddress",
"depth": 32,
"depositSize": 1000000,
"mint": "TokenMintAddress",
"network": "mainnet"
}
FieldTypeDescription
nullifierHashnumber[]Hash of the nullifier (32 bytes as array)
proofnumber[]ZK proof (256 bytes as array)
rootnumber[]Merkle root used in proof (32 bytes)
recipientstringRecipient wallet address (base58)
depthnumberMerkle tree depth (always 32)
depositSizenumberAmount to withdraw (base units)
mintstringToken mint address (base58)
networkstring?Network: “mainnet” or “devnet” (default: mainnet)

Success:

{
"status": 200,
"signature": "5xYz...TransactionSignature"
}

Failure:

{
"status": 400,
"error": "NullifierAlreadySpent"
}
FieldTypeDescription
statusnumberHTTP-like status code
signaturestring?Transaction signature (on success)
errorstring?Error message (on failure)
const response = await fetch('https://worker.turbine.cash/relay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
nullifierHash: Array.from(nullifierHash),
proof: Array.from(proof),
root: Array.from(merkleRoot),
recipient: recipientAddress,
depth: 32,
depositSize: 1000000,
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
network: 'mainnet',
}),
});
const result = await response.json();
if (result.status === 200) {
console.log('Withdrawal successful:', result.signature);
} else {
console.error('Withdrawal failed:', result.error);
}
StatusErrorCause
400NullifierAlreadySpentAlready withdrawn
400InvalidProofProof verification failed
400InvalidRootMerkle root not recognized
400OFACCheckFailedRecipient is sanctioned
500RelayFailedTransaction submission failed

Withdraw and swap tokens via Jupiter.

POST /relay_swap
Content-Type: application/json
{
"nullifierHash": [1, 2, 3, ...],
"proof": [1, 2, 3, ...],
"root": [1, 2, 3, ...],
"recipient": "RecipientWalletAddress",
"depth": 32,
"depositSize": 1000000,
"input_mint": "InputTokenMintAddress",
"output_mint": "OutputTokenMintAddress",
"network": "mainnet"
}
FieldTypeDescription
nullifierHashnumber[]Hash of the nullifier (32 bytes)
proofnumber[]ZK proof (256 bytes)
rootnumber[]Merkle root (32 bytes)
recipientstringRecipient wallet address
depthnumberMerkle tree depth (32)
depositSizenumberAmount to withdraw (base units)
input_mintstringToken being withdrawn
output_mintstringToken to receive after swap
networkstring?Network (default: mainnet)

Same as /relay:

{
"status": 200,
"signature": "5xYz...TransactionSignature"
}
// Withdraw USDC and receive SOL
const response = await fetch('https://worker.turbine.cash/relay_swap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
nullifierHash: Array.from(nullifierHash),
proof: Array.from(proof),
root: Array.from(merkleRoot),
recipient: recipientAddress,
depth: 32,
depositSize: 100000000, // 100 USDC
input_mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
output_mint: 'So11111111111111111111111111111111111111112', // SOL
network: 'mainnet',
}),
});
StatusErrorCause
400SwapFailedJupiter swap failed
400SlippageExceededPrice moved too much
400InsufficientLiquidityNot enough liquidity

Recover funds from a failed deposit.

POST /revert/{network}/{id}/{txn_sig}
Content-Type: application/json

Path Parameters:

ParameterDescription
networkNetwork: “mainnet” or “devnet”
idAuth token
txn_sigOriginal deposit transaction signature

Body:

{
"wallet": "YourWalletAddress",
"signature": "SignatureBase58",
"message": "Revert deposit: OriginalTxSignature"
}
FieldTypeDescription
walletstringWallet that made the deposit
signaturestringSignature of the revert message
messagestringRevert authorization message

Success:

200 OK
"RevertTransactionSignature"
const message = `Revert deposit: ${originalTxSignature}`;
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = await wallet.signMessage(messageBytes);
const response = await fetch(
`https://worker.turbine.cash/revert/mainnet/${authToken}/${originalTxSignature}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: wallet.publicKey.toBase58(),
signature: bs58.encode(signatureBytes),
message,
}),
}
);
const revertTxSig = await response.text();
StatusErrorCause
400DepositNotFoundTransaction doesn’t exist
400AlreadyIndexedUse normal withdraw
400NullifierSpentAlready withdrawn
400InvalidSignatureWrong wallet signed
403NotDepositorWallet didn’t make deposit

EndpointLimit
/relay10 requests/minute
/relay_swap10 requests/minute
/revert5 requests/minute