Submit Payment

POST /v1/payments — submit a signed payment intent to the Axon relayer.

Submit Payment

POST/v1/payments

Submit a signed EIP-712 payment intent for execution. The relayer validates the signature, runs policy checks, and either executes the payment on-chain or routes it through AI verification or human review.

Request Body

Signed Intent Fields

These fields are part of the EIP-712 signed struct and are verified on-chain by the vault contract.

Routing Fields

Off-Chain Metadata

These fields are stored in the relayer's database and surfaced in the owner's dashboard. They are not signed and not sent on-chain.

Response

{
  requestId: string;
  status: "approved" | "pending_review" | "rejected";
  txHash?: string;
  pollUrl?: string;
  estimatedResolutionMs?: number;
  reason?: string;
  simulationResult?: {
    success: boolean;
    gasEstimate?: string;
    error?: string;
  };
}

Response Paths

The relayer evaluates every payment against the vault's policy configuration and routes through one of three paths:

Fast Path

The payment is below all thresholds and passes all policy checks. The relayer submits the transaction on-chain and returns synchronously.

Conditions: amount below AI trigger threshold, no velocity triggers, destination allowed.

{
  "requestId": "req_7f2a9b",
  "status": "approved",
  "txHash": "0x1234567890abcdef..."
}

AI Scan Path

The payment exceeds the AI trigger threshold or velocity window. The relayer runs 3-agent verification (Safety, Behavioral, Reasoning). If 2/3 agents approve, the payment executes. If there is no consensus, it routes to human review.

Conditions: amount above aiTriggerThreshold, or rolling velocity exceeded, or requireAiVerification is true for this bot.

{
  "requestId": "req_7f2a9b",
  "status": "approved",
  "txHash": "0x1234567890abcdef...",
  "estimatedResolutionMs": 28000
}

Or, if no consensus:

{
  "requestId": "req_7f2a9b",
  "status": "pending_review",
  "pollUrl": "/v1/payments/req_7f2a9b"
}

Human Review Path

The payment requires manual approval by the owner via the dashboard. This occurs when AI agents do not reach consensus, or when the owner has configured mandatory human review for the bot.

{
  "requestId": "req_7f2a9b",
  "status": "pending_review",
  "pollUrl": "/v1/payments/req_7f2a9b"
}

Poll GET /v1/payments/:requestId to check when the owner approves or rejects the payment.

Error Responses

StatusCodeDescription
400INVALID_REQUESTMalformed request body or missing required field.
400INVALID_SIGNATUREEIP-712 signature verification failed.
400DEADLINE_EXPIREDThe intent's deadline has already passed.
403BOT_NOT_ACTIVEThe bot address is not whitelisted on the vault.
403EXCEEDS_PER_TX_LIMITAmount exceeds the bot's maxPerTxAmount.
409IDEMPOTENCY_CONFLICTSame idempotency key submitted with different intent data.
422INSUFFICIENT_BALANCEVault does not have enough token balance.
422SIMULATION_FAILEDPre-flight eth_call simulation reverted.
429RATE_LIMITEDToo many requests. Check the Retry-After header.

See Error Codes for the complete reference.

Example

curl

curl -X POST https://relay.axonfi.xyz/v1/payments \
  -H "Content-Type: application/json" \
  -d '{
    "bot": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    "to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
    "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "amount": "10000000",
    "deadline": "1740200000",
    "ref": "0x696e762d30303100000000000000000000000000000000000000000000000000",
    "signature": "0xabcdef...",
    "chainId": 8453,
    "vaultAddress": "0xYourVaultAddress",
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "memo": "Payment for 100 API calls",
    "metadata": {
      "service": "image-generation",
      "requestCount": "100"
    }
  }'

TypeScript (without SDK)

import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
 
const account = privateKeyToAccount('0xYourBotPrivateKey');
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});
 
const intent = {
  bot: account.address,
  to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
  token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  amount: 10_000_000n,
  deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
  ref: '0x696e762d30303100000000000000000000000000000000000000000000000000',
};
 
const signature = await walletClient.signTypedData({
  domain: {
    name: 'AxonVault',
    version: '1',
    chainId: 8453,
    verifyingContract: '0xYourVaultAddress',
  },
  types: {
    PaymentIntent: [
      { name: 'bot', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
      { name: 'ref', type: 'bytes32' },
    ],
  },
  primaryType: 'PaymentIntent',
  message: {
    bot: intent.bot,
    to: intent.to,
    token: intent.token,
    amount: intent.amount,
    deadline: intent.deadline,
    ref: intent.ref,
  },
});
 
const response = await fetch('https://relay.axonfi.xyz/v1/payments', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    ...Object.fromEntries(Object.entries(intent).map(([k, v]) => [k, String(v)])),
    signature,
    chainId: 8453,
    vaultAddress: '0xYourVaultAddress',
    idempotencyKey: crypto.randomUUID(),
    memo: 'Payment for 100 API calls',
  }),
});
 
const result = await response.json();
console.log(result);

Notes

  • The relayer always simulates the transaction via eth_call before submitting on-chain. If simulation fails, no gas is spent and you receive a 422 SIMULATION_FAILED error.
  • The deadline field should be set to a short window (default 5 minutes). Long deadlines increase the risk of stale intents being executed at unfavorable times.
  • The ref field is the only piece of off-chain context that is recorded on-chain. Use it as the anchor linking on-chain transactions to your off-chain records.