Relayer API Reference
The Veridex Relayer provides a REST API for gasless transactions and cross-chain messaging.
Base URL
| Environment | URL |
|---|---|
| Testnet | https://relayer.veridex.network |
| Local | http://localhost:3001 |
Authentication
Most endpoints are public. Rate limiting is applied per IP address.
Health & Info
GET /health
Health check endpoint.
Response:
{
"status": "healthy",
"version": "1.0.0",
"uptime": 3600,
"chains": {
"base-sepolia": "connected",
"optimism-sepolia": "connected",
"arbitrum-sepolia": "connected",
"solana-devnet": "connected"
}
}Status Codes:
| Code | Description |
|---|---|
| 200 | Healthy or degraded |
| 503 | Unhealthy |
GET /api/v1/info
Get relayer configuration.
Response:
{
"relayer": {
"address": "0x...",
"version": "1.0.0"
},
"hub": {
"chainId": 84532,
"wormholeChainId": 10004,
"contracts": {
"hub": "0x..."
}
},
"supportedChains": [
{
"name": "optimism-sepolia",
"chainId": 11155420,
"wormholeChainId": 10005,
"type": "evm"
}
],
"capabilities": {
"gaslessSubmit": true,
"vaaRelay": true,
"balanceQueries": true
}
}Transactions
POST /api/v1/submit
Submit a gasless transaction.
Request Body:
{
"action": "transfer",
"targetChain": 10005,
"payload": {
"token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f5A234",
"amount": "1000000"
},
"signature": {
"r": "0x...",
"s": "0x...",
"authenticatorData": "0x...",
"clientDataJSON": "..."
},
"credentialId": "base64-encoded-id",
"vaultAddress": "0x..."
}Parameters:
| Field | Type | Description |
|---|---|---|
action | string | Action type (transfer, bridge, createVault) |
targetChain | number | Wormhole chain ID |
payload | object | Action-specific payload |
signature | object | WebAuthn signature |
credentialId | string | Base64 credential ID |
vaultAddress | string | User's vault address |
Response:
{
"success": true,
"txHash": "0x...",
"targetChain": 10005,
"estimatedConfirmation": 60
}Error Response:
{
"error": "INVALID_SIGNATURE",
"message": "WebAuthn signature verification failed",
"details": {
"expected": "0x...",
"received": "0x..."
}
}POST /api/v1/submit/query
Submit via Wormhole Query path (faster).
Request Body: Same as /api/v1/submit
Response:
{
"success": true,
"txHash": "0x...",
"path": "query",
"confirmationTime": 5200
}GET /api/v1/status/:txHash
Get transaction status.
Parameters:
| Parameter | Type | Description |
|---|---|---|
txHash | string | Transaction hash |
Response:
{
"txHash": "0x...",
"status": "confirmed",
"blockNumber": 12345678,
"timestamp": 1704067200,
"action": "transfer",
"targetChain": 10005,
"destinationTxHash": "0x..."
}Status Values:
| Status | Description |
|---|---|
pending | Submitted, awaiting confirmation |
attested | VAA attested by guardians |
confirmed | Confirmed on destination chain |
failed | Transaction failed |
Fees
GET /api/v1/fee
Get fee quote.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
targetChain | number | Wormhole chain ID (optional) |
action | string | Action type (optional) |
Response:
{
"fees": {
"wormhole": "100000000000000",
"relayer": "500000000000000",
"total": "600000000000000"
},
"targetChain": 10005,
"estimatedTimeSeconds": 60,
"currency": "ETH"
}Balances
GET /api/v1/balances/:address
Get multi-chain balances for an address.
Parameters:
| Parameter | Type | Description |
|---|---|---|
address | string | Vault address |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
chains | string | Comma-separated chain names (optional) |
Response:
{
"address": "0x...",
"balances": {
"base-sepolia": {
"native": "100000000000000000",
"tokens": {
"0x036CbD53842c5426634e7929541eC2318f3dCF7e": {
"symbol": "USDC",
"decimals": 6,
"balance": "100000000"
}
}
},
"optimism-sepolia": {
"native": "50000000000000000",
"tokens": {}
}
},
"timestamp": 1704067200
}Vaults
POST /api/v1/vault/create
Create a vault on a spoke chain.
Request Body:
{
"targetChain": 10005,
"credentialId": "base64-encoded-id",
"publicKey": {
"x": "0x...",
"y": "0x..."
}
}Response:
{
"success": true,
"vaultAddress": "0x...",
"txHash": "0x...",
"targetChain": 10005
}Stacks Endpoints
The relayer provides Stacks-specific endpoints for direct relay (not Wormhole VAAs).
POST /api/v1/stacks/submit
Submit a sponsored Stacks transaction.
curl -X POST https://relayer.veridex.network/api/v1/stacks/submit \
-H "Content-Type: application/json" \
-d '{
"transaction": "<serialized-stacks-tx-hex>",
"keyHash": "0x..."
}'Response:
{
"success": true,
"txId": "0x7f9e5c51...",
"status": "pending"
}GET /api/v1/stacks/identity/:keyHash
Query identity registration status on the Stacks spoke contract.
curl https://relayer.veridex.network/api/v1/stacks/identity/0x1234...GET /api/v1/stacks/session/:sessionHash
Query session key status.
curl https://relayer.veridex.network/api/v1/stacks/session/0xabcd...GET /api/v1/stacks/vault/:keyHash
Query vault balance and status.
curl https://relayer.veridex.network/api/v1/stacks/vault/0x1234...GET /api/v1/stacks/tx/:txId
Get Stacks transaction status.
curl https://relayer.veridex.network/api/v1/stacks/tx/0x7f9e5c51...Stacks Sponsorship Limits
| Limit | Value |
|---|---|
| Per-identity rate | 50 tx/hour |
| Per-tx fee cap | 0.5 STX |
| Hourly budget | 50 STX |
Cross-Domain Passkey Sharing
Endpoints for third-party app registration, origin validation, and server-side session tokens. See the Cross-Domain Passkeys Guide for full integration instructions.
POST /api/v1/apps/register
Register a new third-party application for cross-domain passkey sharing.
Request Body:
{
"name": "My DeFi App",
"origin": "https://myapp.com",
"description": "A decentralized exchange",
"contactEmail": "dev@myapp.com"
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Application name (min 2 characters) |
origin | string | Yes | HTTPS origin (e.g. https://myapp.com) |
description | string | No | Short description |
contactEmail | string | No | Developer contact email |
Response (201):
{
"success": true,
"app": {
"id": "app_a1b2c3d4e5f6",
"name": "My DeFi App",
"origin": "https://myapp.com",
"apiKey": "vdx_sk_...",
"trustLevel": "registered",
"status": "active",
"createdAt": 1738800000000
}
}Error Responses:
| Code | Description |
|---|---|
| 400 | Invalid name, origin, or non-HTTPS origin |
| 409 | Origin already registered |
GET /api/v1/apps
List registered apps. Public access returns origins only. Admin access (with X-API-Key header matching the metrics API key) returns full details.
Public Response:
{
"origins": ["https://myapp.com", "https://partner.io"],
"count": 2
}GET /api/v1/apps/:appId
Get details of a registered app. Requires the app's API key or admin key via X-API-Key header.
Response:
{
"app": {
"id": "app_a1b2c3d4e5f6",
"name": "My DeFi App",
"origin": "https://myapp.com",
"trustLevel": "registered",
"status": "active",
"requestCount": 42,
"createdAt": 1738800000000,
"apiKey": "vdx_sk_..."
}
}DELETE /api/v1/apps/:appId
Delete a registered app and revoke all its active sessions. Requires the app's API key or admin key via X-API-Key header.
Response:
{
"success": true,
"revokedSessions": 3
}GET /api/v1/apps/info
Get public app info by origin. Used by the Auth Portal to display app details during authentication.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
origin | string | The origin to look up (e.g. https://myapp.com) |
Response:
{
"name": "My DeFi App",
"origin": "https://myapp.com",
"description": "A decentralized exchange",
"trustLevel": "registered",
"registered": true
}GET /api/v1/origins/validate
Check if an origin is allowed for cross-domain passkey sharing.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
origin | string | The origin to validate |
Response:
{
"allowed": true,
"reason": "registered",
"origin": "https://myapp.com"
}Reason values: builtin, development, registered, not_registered, invalid_origin
GET /api/v1/origins/well-known
Generate the .well-known/webauthn JSON dynamically from all built-in and registered origins.
Response:
{
"origins": [
"https://veridex.network",
"https://auth.veridex.network",
"https://sera.veridex.network",
"https://myapp.com"
]
}POST /api/v1/session/create
Create a server-validated cross-origin session token.
Request Body:
{
"keyHash": "0x1234...",
"appOrigin": "https://myapp.com",
"sessionPublicKey": "0xabcd...",
"permissions": ["read", "transfer"],
"expiresInMs": 3600000,
"signature": { "r": "0x...", "s": "0x..." }
}| Field | Type | Required | Description |
|---|---|---|---|
keyHash | string | Yes | User's passkey key hash (0x-prefixed) |
appOrigin | string | Yes | Requesting app's origin |
sessionPublicKey | string | Yes | Session key public key |
permissions | string[] | No | Granted permissions (default: ["read", "transfer"]) |
expiresInMs | number | No | Session duration in ms (default: 3600000 = 1 hour) |
signature | object | Yes | WebAuthn signature proof |
Response (201):
{
"success": true,
"session": {
"id": "ses_abc123",
"keyHash": "0x1234...",
"appOrigin": "https://myapp.com",
"permissions": ["read", "transfer"],
"expiresAt": 1738903600000,
"createdAt": 1738900000000
}
}Error Responses:
| Code | Description |
|---|---|
| 400 | Missing or invalid fields |
| 403 | Origin not registered |
| 404 | Credential not found (user must register a passkey first) |
GET /api/v1/session/:sessionId
Validate a cross-origin session token.
Response (valid):
{
"valid": true,
"session": {
"id": "ses_abc123",
"keyHash": "0x1234...",
"appOrigin": "https://myapp.com",
"permissions": ["read", "transfer"],
"expiresAt": 1738903600000,
"createdAt": 1738900000000
}
}Response (invalid/expired):
{
"valid": false,
"error": "Session not found or expired"
}DELETE /api/v1/session/:sessionId
Revoke a cross-origin session token.
Response:
{
"success": true,
"revoked": true
}Error Codes
| Code | HTTP Status | Description |
|---|---|---|
INVALID_SIGNATURE | 400 | WebAuthn signature verification failed |
INSUFFICIENT_BALANCE | 400 | Vault doesn't have enough tokens |
UNSUPPORTED_CHAIN | 400 | Target chain not configured |
INVALID_PAYLOAD | 400 | Malformed request payload |
NONCE_MISMATCH | 400 | Transaction nonce mismatch |
ORIGIN_NOT_REGISTERED | 403 | Origin not registered for cross-domain auth |
RATE_LIMITED | 429 | Too many requests |
RELAYER_ERROR | 500 | Internal relayer error |
CHAIN_UNAVAILABLE | 503 | Target chain not responding |
VAA_NOT_FOUND | 404 | Wormhole VAA not yet available |
STACKS_SPONSORSHIP_LIMIT | 429 | Stacks sponsorship rate limit exceeded |
STACKS_POST_CONDITION_FAIL | 400 | Stacks post-condition validation failed |
Rate Limits
| Endpoint | Limit |
|---|---|
| All endpoints | 100 requests/minute per IP |
/api/v1/submit | 10 requests/minute per vault |
/api/v1/balances | 30 requests/minute per IP |
WebSocket (Coming Soon)
Real-time transaction status updates:
const ws = new WebSocket('wss://relayer.veridex.network/ws');
ws.send(JSON.stringify({
type: 'subscribe',
txHash: '0x...'
}));
ws.onmessage = (event) => {
const { type, data } = JSON.parse(event.data);
if (type === 'status') {
console.log('Status update:', data);
}
};Self-Hosting
See the Relayer README for self-hosting instructions.
# Clone and setup
git clone https://github.com/Veridex-Protocol/demo
cd demo/packages/relayer
cp .env.example .env
# Configure .env with your keys
# Start relayer
npm run dev