Private Transfers API
API endpoints for private withdrawals
Private Transfers API
Section titled “Private Transfers API”Core endpoints for private withdrawals via the relayer.
POST /relay
Section titled “POST /relay”Submit a private withdrawal.
Request
Section titled “Request”POST /relayContent-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"}| Field | Type | Description |
|---|---|---|
nullifierHash | number[] | Hash of the nullifier (32 bytes as array) |
proof | number[] | ZK proof (256 bytes as array) |
root | number[] | Merkle root used in proof (32 bytes) |
recipient | string | Recipient wallet address (base58) |
depth | number | Merkle tree depth (always 32) |
depositSize | number | Amount to withdraw (base units) |
mint | string | Token mint address (base58) |
network | string? | Network: “mainnet” or “devnet” (default: mainnet) |
Response
Section titled “Response”Success:
{ "status": 200, "signature": "5xYz...TransactionSignature"}Failure:
{ "status": 400, "error": "NullifierAlreadySpent"}| Field | Type | Description |
|---|---|---|
status | number | HTTP-like status code |
signature | string? | Transaction signature (on success) |
error | string? | Error message (on failure) |
Example
Section titled “Example”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);}Errors
Section titled “Errors”| Status | Error | Cause |
|---|---|---|
| 400 | NullifierAlreadySpent | Already withdrawn |
| 400 | InvalidProof | Proof verification failed |
| 400 | InvalidRoot | Merkle root not recognized |
| 400 | OFACCheckFailed | Recipient is sanctioned |
| 500 | RelayFailed | Transaction submission failed |
POST /relay_swap
Section titled “POST /relay_swap”Withdraw and swap tokens via Jupiter.
Request
Section titled “Request”POST /relay_swapContent-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"}| Field | Type | Description |
|---|---|---|
nullifierHash | number[] | Hash of the nullifier (32 bytes) |
proof | number[] | ZK proof (256 bytes) |
root | number[] | Merkle root (32 bytes) |
recipient | string | Recipient wallet address |
depth | number | Merkle tree depth (32) |
depositSize | number | Amount to withdraw (base units) |
input_mint | string | Token being withdrawn |
output_mint | string | Token to receive after swap |
network | string? | Network (default: mainnet) |
Response
Section titled “Response”Same as /relay:
{ "status": 200, "signature": "5xYz...TransactionSignature"}Example
Section titled “Example”// Withdraw USDC and receive SOLconst 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', }),});Additional Errors
Section titled “Additional Errors”| Status | Error | Cause |
|---|---|---|
| 400 | SwapFailed | Jupiter swap failed |
| 400 | SlippageExceeded | Price moved too much |
| 400 | InsufficientLiquidity | Not enough liquidity |
POST /revert/{network}/{id}/{txn_sig}
Section titled “POST /revert/{network}/{id}/{txn_sig}”Recover funds from a failed deposit.
Request
Section titled “Request”POST /revert/{network}/{id}/{txn_sig}Content-Type: application/jsonPath Parameters:
| Parameter | Description |
|---|---|
network | Network: “mainnet” or “devnet” |
id | Auth token |
txn_sig | Original deposit transaction signature |
Body:
{ "wallet": "YourWalletAddress", "signature": "SignatureBase58", "message": "Revert deposit: OriginalTxSignature"}| Field | Type | Description |
|---|---|---|
wallet | string | Wallet that made the deposit |
signature | string | Signature of the revert message |
message | string | Revert authorization message |
Response
Section titled “Response”Success:
200 OK"RevertTransactionSignature"Example
Section titled “Example”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();Errors
Section titled “Errors”| Status | Error | Cause |
|---|---|---|
| 400 | DepositNotFound | Transaction doesn’t exist |
| 400 | AlreadyIndexed | Use normal withdraw |
| 400 | NullifierSpent | Already withdrawn |
| 400 | InvalidSignature | Wrong wallet signed |
| 403 | NotDepositor | Wallet didn’t make deposit |
Rate Limits
Section titled “Rate Limits”| Endpoint | Limit |
|---|---|
/relay | 10 requests/minute |
/relay_swap | 10 requests/minute |
/revert | 5 requests/minute |