Payment Flow
How payments move through Axon: from bot-signed intent to on-chain execution across three response paths.
Payment Flow
Every payment in Axon starts with a bot signing an EIP-712 intent and ends with an ERC-20 transfer from the vault. What happens in between depends on the transaction's risk profile. There are three paths: fast, AI scan, and human review.
Overview
Bot signs PaymentIntent (EIP-712)
|
v
POST /v1/payments (to Relayer API)
|
v
Relayer validates signature + deadline + bot whitelist
|
v
Simulation via eth_call (pre-flight check)
|
v
Policy Engine evaluates
|
+--> Fast Path ---------> executePayment() --> txHash (sync)
|
+--> AI Scan Path ------> 3-agent verification --> consensus? --> executePayment()
| |
| +--> no consensus --> Human Review
|
+--> Human Review Path --> Owner notified --> approve/reject (async)
Step-by-Step
1. Bot Signs Intent
The bot creates a PaymentIntent and signs it with its private key using EIP-712 typed structured data:
const intent = {
bot: botAddress,
to: recipientAddress,
token: USDC_ADDRESS,
amount: 50_000_000n, // 50 USDC
deadline: Math.floor(Date.now() / 1000) + 300, // 5 minutes
ref: keccak256(toUtf8Bytes('invoice-001')),
};
const signature = await bot.signTypedData(domain, types, intent);The bot's private key never leaves its environment. The signed intent is submitted to the relayer API.
2. Relayer Intake
The relayer receives the intent via POST /v1/payments and performs initial validation:
- Signature format -- is it a valid ECDSA signature?
- Deadline -- is the intent still within its validity window?
- Bot whitelist -- is this bot registered and active in the target vault?
- Idempotency -- has this
idempotencyKeybeen seen before? If yes, return the original response.
If any check fails, the relayer returns an error immediately. No gas is spent.
3. Simulation
Before submitting anything on-chain, the relayer dry-runs the transaction via eth_call:
- Catches expired deadlines
- Catches invalid or already-used signatures
- Catches insufficient vault balance
- Catches destination contract reverts
If simulation fails, the relayer returns a descriptive error to the bot. No gas is spent.
4. Policy Engine
The relayer evaluates the intent against the bot's configured policies (read from the on-chain BotConfig):
- Per-transaction limit -- does the amount exceed
maxPerTxAmount? - Spending limits -- does this transaction push the bot over any of its
SpendingLimitwindows (e.g., daily, hourly)? - Velocity check -- has this bot's spending velocity spiked in the recent window?
- Destination whitelist -- is the recipient in the vault's allowed destinations?
Based on the evaluation, the intent is routed to one of three paths.
Path 1: Fast Path
Trigger: Transaction is below all thresholds, no velocity anomaly, destination is whitelisted or no whitelist is configured.
Behavior: Synchronous. The relayer submits the transaction on-chain immediately.
Bot --> Relayer --> executePayment() --> txHash
Response:
{
"requestId": "req_abc123",
"status": "approved",
"txHash": "0x...",
"chainId": 8453
}Timing: Typically under 2 seconds end-to-end (signature verification + simulation + on-chain confirmation on L2).
HTTP 402 flows must use the fast path. If a payment is destined for an HTTP 402 resource unlock, the bot needs a synchronous txHash before the resource is released. Owners should pre-whitelist trusted 402 destinations so these payments are never routed to AI scan or human review.
Path 2: AI Scan Path
Trigger: One or more of these conditions:
- Single transaction amount exceeds the bot's
aiTriggerThreshold - Bot's spending velocity in the recent window exceeds the configured velocity threshold
- Bot has
requireAiVerificationset totrue
Behavior: The relayer holds the transaction and sends it through the 3-agent AI verification system (Safety, Behavioral, Reasoning agents running in parallel).
Bot --> Relayer --> Policy Engine (threshold triggered)
|
v
AI Verification (3 agents, parallel)
|
+--> 2/3 consensus: APPROVE --> executePayment() --> txHash
|
+--> 2/3 consensus: REJECT --> rejected, bot notified
|
+--> No consensus --> routed to Human Review
If consensus is reached quickly (under ~30 seconds): the response is still synchronous. The bot gets a txHash or rejection in the same HTTP response.
{
"requestId": "req_def456",
"status": "approved",
"txHash": "0x...",
"chainId": 8453,
"verification": {
"triggered": true,
"result": "approved",
"agents": {
"safety": "approve",
"behavioral": "approve",
"reasoning": "approve"
},
"latencyMs": 18200
}
}If no consensus is reached: the intent is routed to human review (Path 3).
Timing: Target p95 latency under 30 seconds for the AI scan itself.
Path 3: Human Review Path
Trigger: One of:
- AI verification returned no consensus (e.g., 1 approve, 1 reject, 1 abstain)
- The owner has configured manual review for this bot
- An AI agent flagged a high-severity concern
Behavior: Asynchronous. The intent is placed in the owner's review queue. The owner is notified via push notification (PWA) and can approve or reject with a single tap.
Bot --> Relayer --> Policy Engine / AI Scan --> no consensus
|
v
Human Review Queue
|
Owner notified (push)
|
Approve / Reject
|
v
executePayment() or rejection
Immediate response to bot:
{
"requestId": "req_ghi789",
"status": "pending_review",
"pollUrl": "/v1/payments/req_ghi789",
"estimatedResolutionMs": 300000
}The bot can poll for the payment status:
GET /v1/payments/req_ghi789
Timing: Depends on the owner's response time. The intent's deadline still applies -- if the owner does not act before the deadline expires, the intent becomes invalid and must be re-signed by the bot.
Payment Request and Response Format
Request
POST /v1/payments
{
"bot": "0x...",
"to": "0x...",
"token": "0x...",
"amount": "50000000",
"deadline": "1752000000",
"ref": "0x...",
"signature": "0x...",
"chainId": 8453,
"vaultAddress": "0x...",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
"memo": "Payment for 100 API calls",
"resourceUrl": "https://api.example.com/v1/data"
}Fields above the blank line (bot, to, token, amount, deadline, ref, signature, chainId, vaultAddress) are verified on-chain. Fields below (idempotencyKey, memo, resourceUrl) are stored in PostgreSQL only and never touch the blockchain.
The idempotencyKey is mandatory. If the same key is submitted twice, the relayer returns the original response without re-executing.
Status Check
GET /v1/payments/{requestId}
{
"requestId": "req_ghi789",
"status": "approved",
"txHash": "0x...",
"chainId": 8453,
"resolvedAt": "2026-07-15T14:30:00Z"
}Possible status values: "approved", "pending_review", "rejected".