First Payment

End-to-end walkthrough of making your first payment through an Axon vault.

First Payment

This guide walks through a complete payment from start to finish, covering initialization, sending, handling the three possible response paths, and confirming the result on-chain.

Prerequisites

Before starting, ensure you have:

  • A deployed vault with USDC deposited (Deploy a Vault)
  • A registered bot with its address whitelisted on the vault (Register a Bot)
  • The bot's private key available in your environment
  • @axonfi/sdk installed (npm install @axonfi/sdk)

Step 1: Initialize the SDK

import { AxonClient, decryptKeystore, Chain, Token, encodeRef } from '@axonfi/sdk';
import fs from 'fs';
 
// Decrypt bot key from encrypted keystore file
const keystore = fs.readFileSync(process.env.AXON_BOT_KEYSTORE_PATH!, 'utf8');
const botPrivateKey = await decryptKeystore(keystore, process.env.AXON_BOT_PASSPHRASE!);
 
const axon = new AxonClient({
  botPrivateKey,
  vaultAddress: process.env.AXON_VAULT_ADDRESS as `0x${string}`,
  chainId: Chain.Base,
});

The AxonClient derives your bot's public address from the private key. The decrypted key stays in memory and is used only for EIP-712 signing — it is never sent to the relayer.

Step 2: Create and Send a Payment

const result = await axon.pay({
  to: '0xRecipientAddress',
  token: 'USDC',
  amount: 25,                            // 25 USDC — SDK handles decimals
  ref: encodeRef('invoice-042'),         // bytes32 on-chain reference
  memo: 'Payment for data processing job #42',
});

Under the hood, the SDK:

  1. Constructs the PaymentIntent struct with your bot address, a deadline (default: 5 minutes from now), and the provided fields
  2. Signs the struct as EIP-712 typed data using your bot's private key
  3. Generates an idempotency key (UUID) to prevent double-processing
  4. Submits the signed intent to the relayer at POST /v1/payments

Step 3: Handle the Response

The response shape depends on which path the relayer takes.

Fast Path (Synchronous)

If the payment is below all policy thresholds, you get a confirmed transaction hash immediately:

if (result.status === 'approved') {
  console.log('Payment confirmed on-chain!');
  console.log('Transaction:', result.txHash);
  // Done — no further action needed
}

AI Scan Path (Short Async)

If the payment triggered AI verification (amount exceeded aiTriggerThreshold or velocity window), the relayer holds the request while the three agents vote. This typically resolves within 30 seconds.

if (result.status === 'pending_review' && result.estimatedResolutionMs) {
  console.log('AI verification in progress...');
  console.log('Estimated wait:', result.estimatedResolutionMs, 'ms');
 
  // Poll until resolved
  let final = await axon.poll(result.requestId);
  while (final.status === 'pending_review') {
    await new Promise((r) => setTimeout(r, 3_000));
    final = await axon.poll(result.requestId);
  }
 
  if (final.status === 'approved') {
    console.log('Payment approved by AI and confirmed:', final.txHash);
  } else {
    console.log('Payment rejected:', final.reason);
  }
}

Human Review Path (Long Async)

If AI agents did not reach consensus, or if the bot is configured with requireAiVerification: true and manual review, the payment enters the owner's review queue.

if (result.status === 'pending_review') {
  console.log('Payment requires human approval.');
  console.log('Request ID:', result.requestId);
  console.log('Poll URL:', result.pollUrl);
 
  // Poll periodically until resolved
  let checkLater = await axon.poll(result.requestId);
  while (checkLater.status === 'pending_review') {
    await new Promise((r) => setTimeout(r, 30_000));
    checkLater = await axon.poll(result.requestId);
  }
}

Step 4: Complete Response Handler

Here is a robust handler that covers all three paths:

async function handlePayment(result: PaymentResult): Promise<string | null> {
  switch (result.status) {
    case 'approved':
      // Fast path — already confirmed
      return result.txHash;
 
    case 'pending_review':
      // AI scan or human review — poll until resolved
      let resolved = await axon.poll(result.requestId);
      while (resolved.status === 'pending_review') {
        await new Promise((r) => setTimeout(r, 5_000));
        resolved = await axon.poll(result.requestId);
      }
      return resolved.status === 'approved' ? resolved.txHash : null;
 
    default:
      return null;
  }
}
 
const txHash = await handlePayment(result);
if (txHash) {
  console.log('Confirmed:', txHash);
}

Step 5: Verify On-Chain

Once you have a txHash, you can confirm the transfer on a block explorer:

  • Base: https://basescan.org/tx/{txHash}
  • Arbitrum: https://arbiscan.io/tx/{txHash}
  • Base Sepolia: https://sepolia.basescan.org/tx/{txHash}

Or verify programmatically:

import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';
 
const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});
 
const receipt = await publicClient.getTransactionReceipt({
  hash: txHash as `0x${string}`,
});
 
console.log('Block number:', receipt.blockNumber);
console.log('Status:', receipt.status); // "success"

Using the Raw API

If you are not using the TypeScript SDK, you can call the relayer API directly:

POST/v1/payments

| Parameter | Type | Required | Description | |---|---|---|---| | bot | string | Yes | Bot address that signed the intent | | to | string | Yes | Recipient address | | token | string | Yes | ERC-20 token address | | amount | string | Yes | Amount in base units (e.g. "25000000" for 25 USDC) | | deadline | string | Yes | Unix timestamp as decimal string | | ref | string | Yes | bytes32 hex string (0x-prefixed) | | signature | string | Yes | EIP-712 signature of the intent | | chainId | number | Yes | Target chain ID (e.g., 8453 for Base) | | vaultAddress | string | Yes | The vault contract address | | idempotencyKey | string | Yes | UUID to prevent double-processing | | memo | string | No | Human-readable payment purpose | | metadata | object | No | Arbitrary key-value pairs (max 10 keys) |

curl -X POST https://relay.axonfi.xyz/v1/payments \
  -H "Content-Type: application/json" \
  -d '{
    "bot": "0xBotAddress",
    "to": "0xRecipient",
    "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "amount": "25000000",
    "deadline": "1756339200",
    "ref": "0x696e766f6963652d303432000000000000000000000000000000000000000000",
    "signature": "0x...",
    "chainId": 8453,
    "vaultAddress": "0xYourVault",
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "memo": "Payment for data processing job #42"
  }'

Next Steps