Skip to main content

Overview

The relayer API enables privacy-preserving withdrawals from shielded pools by submitting zero-knowledge proofs on behalf of users. Users never need to expose their Starknet wallet addresses.

POST /api/relayer/withdraw

Submits a Groth16 withdrawal proof to a shielded pool. The relayer broadcasts the transaction and deducts a fee from the withdrawn amount.

Request Body

poolAddress
string
required
Shielded pool contract address (V4 pool)
calldata
array
required
Groth16 proof calldata from generateVerifyCalldata(). Array of felt252 hex strings including proof points, public inputs, and verification hints.

Response

transaction_hash
string
Starknet transaction hash
execution_status
string
Transaction execution status: SUCCEEDED, REVERTED, or UNKNOWN
revert_reason
string
Revert reason if transaction failed

Example Request

const proof = await generateWithdrawalProof(note, merkleProof, recipient, relayer, fee, batchStart, batchSize);
const calldata = await generateVerifyCalldata(proof.proof, proof.publicSignals);

const response = await fetch('/api/relayer/withdraw', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ poolAddress: '0x...', calldata })
});

const result = await response.json();
console.log('Withdrawal tx:', result.transaction_hash);

Gas Configuration

Source: /home/daytona/workspace/source/src/app/api/relayer/withdraw/route.ts:50 Withdrawal transactions use generous gas bounds due to Groth16 verification (~34M L2 gas):
  • L2 gas: 1.2B units with 3x price multiplier
  • L1 gas: 256 units
  • L1 data gas: 20,480 units

POST /api/relayer/deploy-batch

Deploys a batch of pending deposits from a shielded pool to the underlying vault. This makes deposits active and eligible for yield.

Request Body

poolAddress
string
required
Shielded pool contract address
count
number
required
Number of deposits to deploy (typically 3 for V4 pools)
partial
boolean
default:false
If true, calls deploy_partial_batch instead of deploy_batch

Response

transaction_hash
string
Transaction hash of the batch deployment
execution_status
string
Execution status

Example Request

curl -X POST https://sable.money/api/relayer/deploy-batch \
  -H "Content-Type: application/json" \
  -d '{
    "poolAddress": "0x002bdb9769851d0307e812351cc1eb31b617951fba786cfd5d58baff36589a33",
    "count": 3
  }'

GET /api/relayer/pool-status

Fetches the current state of a shielded pool including denomination, undeployed deposits, and fee configuration.

Query Parameters

pool
string
required
Pool contract address

Response

denomination
string
Fixed deposit size in WBTC sats (for V4) or USDC cents (for stablecoin pools)
undeployedCount
number
Number of deposits awaiting batch deployment
activeDeposits
number
Number of deposits currently deployed in vault
totalDeposits
number
Total lifetime deposits
totalAssets
string
Total WBTC/USDC currently in pool (includes yield)
maxFeeBps
number
Maximum relayer fee in basis points (e.g., 500 = 5%)
maxFee
string
Maximum relayer fee in sats
nextIndex
number
Next leaf index for new deposits

Example Request

curl "https://sable.money/api/relayer/pool-status?pool=0x002bdb9769..."

Example Response

{
  "denomination": "20000",
  "undeployedCount": 2,
  "activeDeposits": 15,
  "totalDeposits": 17,
  "totalAssets": "345230",
  "maxFeeBps": 500,
  "maxFee": "1000",
  "nextIndex": 17
}

GET /api/relayer/fee-estimate

Estimates the relayer fee for a complete deposit-withdraw cycle. Fee is calculated based on current gas prices and includes a 20% margin.

Query Parameters

pool
string
Pool address to check contract max_fee (fee will be capped at this value)

Response

feeSats
number
Recommended relayer fee in WBTC sats (includes 20% margin)
feeBreakdown
object
Detailed gas cost breakdown:
  • depositGasStrk: Deposit gas cost in STRK
  • deployBatchGasStrk: Batch deployment cost per deposit (amortized)
  • withdrawGasStrk: Withdrawal gas cost in STRK
  • totalGasStrk: Total gas cost in STRK
  • totalGasUsd: Total gas cost in USD
  • marginPercent: Margin percentage (20%)
gasPrices
object
Current gas prices:
  • l2GasPriceFri: L2 gas price in fri
  • l1DataGasPriceFri: L1 data gas price in fri
prices
object
Market prices used:
  • strkUsd: STRK price in USD
  • btcUsd: BTC price in USD
maxFeeSats
number
Pool’s max_fee if pool parameter was provided
denomination
number
Pool denomination if pool parameter was provided

Example Request

curl "https://sable.money/api/relayer/fee-estimate?pool=0x002bdb9769..."

Example Response

{
  "feeSats": 450,
  "feeBreakdown": {
    "depositGasStrk": 0.0012,
    "deployBatchGasStrk": 0.0008,
    "withdrawGasStrk": 0.0425,
    "totalGasStrk": 0.0445,
    "totalGasUsd": 0.0178,
    "marginPercent": 20
  },
  "gasPrices": {
    "l2GasPriceFri": "8589934592",
    "l1DataGasPriceFri": "35184372088832"
  },
  "prices": {
    "strkUsd": 0.42,
    "btcUsd": 102500
  },
  "maxFeeSats": 1000,
  "denomination": 20000
}

POST /api/relayer/private-swap

Execute a private token swap by withdrawing from a WBTC pool and swapping to another token via AVNU. Source: /home/daytona/workspace/source/src/app/api/relayer/private-swap/route.ts

Request Body

wbtcPool
string
required
Address of the WBTC input pool to withdraw from
proofCalldata
string[]
required
Groth16 proof calldata array for withdrawal verification
newCommitment
string
required
New commitment for the output token pool
outputToken
string
required
Address of the token to receive (ETH, USDC, STRK, etc.)
slippage
number
Maximum slippage tolerance (e.g., 0.02 = 2%)

Response

transaction_hash
string
StarkNet transaction hash
execution_status
string
Transaction status (SUCCEEDED, REVERTED, or UNKNOWN)
revert_reason
string | null
Revert reason if execution failed
leafIndex
number
Merkle tree leaf index of the new commitment (-1 if failed)
outputAmount
string
Hex-encoded amount of output token received

Example Request

curl -X POST https://sable.app/api/relayer/private-swap \
  -H "Content-Type: application/json" \
  -d '{
    "wbtcPool": "0x0a9ca1d554d7ef360e47de70f89fefcb571b568c8dd049de96fa181e1c69c60",
    "proofCalldata": ["0x1234...", "0x5678..."],
    "newCommitment": "0x789abc...",
    "outputToken": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
    "slippage": 0.02
  }'
Private swaps require ~40M L2 gas due to Groth16 verification (~35M), AVNU swap (~3M), and Merkle insert (~1M).

Gas Estimates

Source: /home/daytona/workspace/source/src/app/api/relayer/fee-estimate/route.ts:16
OperationL2 GasDescription
deposit800,000Merkle tree insert + transferFrom
deploy_batch1,200,000Vault deposit + share accounting (per batch)
withdraw35,000,000Groth16 verification + vault redemption
private_swap40,000,000Groth16 + AVNU swap + Merkle insert
Withdrawal and private swap are the most expensive operations due to on-chain Groth16 proof verification via Garaga.

Build docs developers (and LLMs) love