Docs/Relayer/Api Reference

Relayer API Reference

HTTP API endpoints for the ZKMix relayer

Relayer API Reference

The ZKMix relayer exposes an HTTP REST API that clients use to submit withdrawal requests and query relayer status. This document describes all available endpoints, their request and response formats, and error handling.

All endpoints accept and return JSON. The relayer listens on the configured port (default: 8080) and is typically deployed behind a reverse proxy (nginx, Caddy) with TLS termination for production use.

Base URL

https://<relayer-hostname>:<port>

For the official ZKMix relayer on devnet:

https://relayer-devnet.zkmix.dev

Authentication

The relayer API does not require authentication. Any client can submit a withdrawal request. Rate limiting is applied per IP address to prevent abuse.

Endpoints

POST /relay

Submit a withdrawal proof for the relayer to process. This is the primary endpoint that users and the SDK interact with.

Request Body:

json
{
  "proof": {
    "a": [
      "0x2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a",
      "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a"
    ],
    "b": [
      [
        "0x3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
        "0x4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a"
      ],
      [
        "0x5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a",
        "0x6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a"
      ]
    ],
    "c": [
      "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a",
      "0x8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a"
    ]
  },
  "root": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "nullifierHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
  "recipient": "8xQmK4pR7vN2wL5jH3nF9bC6dA1eG0mT8yU4iO2pS7qW",
  "poolAddress": "ZKMxPool1SOL111111111111111111111111111111111",
  "relayerFee": 3000000
}

Request Fields:

FieldTypeRequiredDescription
proofobjectYesGroth16 proof containing a, b, and c points
proof.astring[]YesG1 point as two hex-encoded field elements
proof.bstring[][]YesG2 point as 2x2 hex-encoded field elements
proof.cstring[]YesG1 point as two hex-encoded field elements
rootstringYesMerkle root the proof was generated against
nullifierHashstringYesHash of the nullifier (prevents double-spending)
recipientstringYesBase58-encoded Solana address to receive funds
poolAddressstringYesBase58-encoded address of the mixer pool
relayerFeenumberYesFee to pay the relayer in smallest token units

Success Response (200 OK):

json
{
  "success": true,
  "txSignature": "5K7pQr8sT2nU4vW6xY0zA1bC3dE5fG7hI9jK0lM1nO2pQ3rS4tU5vW6xY7zA8bC",
  "message": "Withdrawal relayed successfully",
  "blockTime": 1710512400,
  "slot": 245678901
}

Error Response (4xx/5xx):

json
{
  "success": false,
  "error": "NULLIFIER_ALREADY_SPENT",
  "message": "The provided nullifier hash has already been used in a previous withdrawal"
}

Possible Error Codes:

HTTP StatusError CodeDescription
400INVALID_PROOF_FORMATProof data is malformed or missing fields
400INVALID_RECIPIENTRecipient is not a valid Solana address
400INVALID_POOLPool address not recognized or not supported
400UNKNOWN_MERKLE_ROOTThe provided root is not in the root history
400NULLIFIER_ALREADY_SPENTThis nullifier has been used before
400FEE_TOO_LOWThe relayer fee is below the relayer's minimum
400FEE_TOO_HIGHThe fee exceeds the pool denomination
422PROOF_VERIFICATION_FAILEDThe Groth16 proof is invalid
429RATE_LIMITEDToo many requests from this IP address
500TRANSACTION_FAILEDThe transaction was submitted but failed on-chain
500RPC_ERRORFailed to communicate with the Solana RPC node
503RELAYER_BUSYToo many pending transactions, try again later
503INSUFFICIENT_BALANCERelayer lacks SOL to pay the transaction fee

GET /status

Returns the current status and capabilities of the relayer. Useful for the SDK to determine whether a relayer is operational and what pools it supports.

Request:

GET /status

No request body or parameters required.

Response (200 OK):

json
{
  "relayerAddress": "7xKpM3nQr4sT5vW6xY0zA1bC3dE5fG7hI9jK0lM1nO2",
  "version": "1.2.0",
  "network": "mainnet-beta",
  "isReady": true,
  "uptime": 172800,
  "stats": {
    "totalRelayed": 4521,
    "last24hRelayed": 187,
    "successRate": 0.994
  },
  "supportedPools": [
    {
      "address": "ZKMxPool01SOL111111111111111111111111111111111",
      "token": "SOL",
      "denomination": 100000000,
      "denominationFormatted": "0.1 SOL"
    },
    {
      "address": "ZKMxPool1SOL1111111111111111111111111111111111",
      "token": "SOL",
      "denomination": 1000000000,
      "denominationFormatted": "1 SOL"
    },
    {
      "address": "ZKMxPool10SOL11111111111111111111111111111111",
      "token": "SOL",
      "denomination": 10000000000,
      "denominationFormatted": "10 SOL"
    }
  ],
  "queueDepth": 2,
  "maxQueueDepth": 50
}

Response Fields:

FieldTypeDescription
relayerAddressstringThe relayer's Solana address
versionstringRelayer software version
networkstringSolana cluster the relayer is connected to
isReadybooleanWhether the relayer is accepting requests
uptimenumberSeconds since the relayer started
statsobjectAggregate statistics
supportedPoolsarrayPools this relayer will process withdrawals for
queueDepthnumberNumber of transactions currently being processed
maxQueueDepthnumberMaximum concurrent transactions

GET /fees

Returns the current fee schedule for this relayer. Fees can vary by pool and may be adjusted dynamically based on network conditions.

Request:

GET /fees

No request body or parameters required.

Response (200 OK):

json
{
  "feePercentage": 0.3,
  "minFee": 10000,
  "fees": [
    {
      "poolAddress": "ZKMxPool01SOL111111111111111111111111111111111",
      "token": "SOL",
      "denomination": 100000000,
      "feeAmount": 300000,
      "feeFormatted": "0.0003 SOL",
      "feePercentage": 0.3
    },
    {
      "poolAddress": "ZKMxPool1SOL1111111111111111111111111111111111",
      "token": "SOL",
      "denomination": 1000000000,
      "feeAmount": 3000000,
      "feeFormatted": "0.003 SOL",
      "feePercentage": 0.3
    },
    {
      "poolAddress": "ZKMxPool10SOL11111111111111111111111111111111",
      "token": "SOL",
      "denomination": 10000000000,
      "feeAmount": 30000000,
      "feeFormatted": "0.03 SOL",
      "feePercentage": 0.3
    }
  ],
  "gasCostEstimate": 5000,
  "priorityFeeEstimate": 100000,
  "lastUpdated": "2025-03-15T14:30:22.451Z"
}

Response Fields:

FieldTypeDescription
feePercentagenumberDefault fee as a percentage of denomination
minFeenumberMinimum fee in lamports/token units
feesarrayPer-pool fee breakdown
fees[].feeAmountnumberCalculated fee for this pool in smallest units
fees[].feeFormattedstringHuman-readable fee amount
gasCostEstimatenumberEstimated Solana transaction fee in lamports
priorityFeeEstimatenumberCurrent estimated priority fee in lamports
lastUpdatedstringISO 8601 timestamp of last fee recalculation

GET /health

A lightweight health check endpoint intended for monitoring systems and load balancers.

Request:

GET /health

Response (200 OK):

json
{
  "status": "healthy",
  "solBalance": 2450000000,
  "rpcConnected": true,
  "timestamp": "2025-03-15T14:30:22.451Z"
}

Response (503 Service Unavailable):

json
{
  "status": "unhealthy",
  "reason": "RPC connection lost",
  "solBalance": 0,
  "rpcConnected": false,
  "timestamp": "2025-03-15T14:30:22.451Z"
}

The health endpoint returns 200 when the relayer is fully operational and 503 when it cannot process requests. Unhealthy conditions include:

  • RPC connection failure
  • SOL balance too low to pay transaction fees
  • Too many pending transactions (queue full)

Request Validation

The relayer performs several validation checks before submitting a transaction to the network:

  1. Format validation -- All fields are present and correctly formatted. Proof points are valid hex strings of the correct length. Addresses are valid Base58-encoded Solana public keys.
  1. Pool validation -- The pool address corresponds to a known, active ZKMix pool that this relayer supports.
  1. Fee validation -- The relayer fee meets the relayer's minimum fee requirement. The fee does not exceed the pool denomination.
  1. Nullifier check -- The relayer queries the on-chain NullifierSet to verify the nullifier has not been spent. This pre-check avoids submitting transactions that are guaranteed to fail on-chain, saving the relayer gas costs.
  1. Root check -- The relayer verifies the provided Merkle root matches a known root in the on-chain root history.

These pre-flight checks are performed before the relayer constructs and signs the transaction. If any check fails, the relayer returns an appropriate error response without incurring any on-chain costs.

Rate Limiting

The relayer enforces rate limits to prevent abuse and denial-of-service attacks:

LimitDefaultDescription
Global requests/minute60Total requests across all clients
Per-IP requests/minute10Requests from a single IP address
Concurrent transactions50Maximum in-flight transactions

When rate limited, the relayer returns a 429 status code with a Retry-After header indicating how many seconds the client should wait before retrying:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "success": false,
  "error": "RATE_LIMITED",
  "message": "Rate limit exceeded. Please retry after 30 seconds.",
  "retryAfter": 30
}

CORS Configuration

The relayer supports CORS to allow browser-based clients (such as the ZKMix web app) to interact with the API directly:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

For production deployments, it is recommended to restrict Access-Control-Allow-Origin to specific trusted domains rather than using the wildcard *.

SDK Integration

The ZKMix SDK handles all relayer API interaction automatically. You do not need to call these endpoints directly unless you are building a custom integration:

typescript
import { ZKMix } from "@zkmix/sdk";

const zkmix = new ZKMix({ connection, cluster: "mainnet-beta" });

// The SDK calls GET /status and GET /fees internally
// to select the best relayer, then calls POST /relay
const result = await zkmix.withdraw({
  deposit: savedDepositNote,
  recipient: newAddress,
  useRelayer: true,
});