AxonClient
Complete reference for the AxonClient class — the primary interface for sending payments through Axon.
AxonClient
AxonClient is the main entry point for the Axon TypeScript SDK. It wraps EIP-712 signing, relayer communication, and status polling into a single ergonomic class.
import { AxonClient } from '@axonfi/sdk';Constructor
new AxonClient(config: AxonClientConfig)Creates a new client instance bound to a specific vault, chain, and relayer.
AxonClientConfig
| Parameter | Type | Required | Description |
|---|---|---|---|
| vaultAddress | Address | Required | The on-chain address of the AxonVault this client operates against. |
| chainId | number | Required | The EVM chain ID where the vault is deployed (e.g. 8453 for Base, 42161 for Arbitrum). |
| botPrivateKey | Hex | Optional | The bot's private key as a 0x-prefixed hex string. Used to derive the bot address and sign EIP-712 intents locally. If omitted, you must use the standalone signPayment() function. |
Option 1 — Encrypted keystore (recommended):
import { AxonClient, decryptKeystore } from '@axonfi/sdk';
import { readFileSync } from 'fs';
const keystore = readFileSync(process.env.AXON_BOT_KEYSTORE_PATH!, 'utf-8');
const botPrivateKey = await decryptKeystore(keystore, process.env.AXON_BOT_PASSPHRASE!);
const axon = new AxonClient({
vaultAddress: '0x1234...abcd',
chainId: 8453,
botPrivateKey,
});Option 2 — Private key from environment variable:
const axon = new AxonClient({
vaultAddress: '0x1234...abcd',
chainId: 8453,
botPrivateKey: process.env.AXON_BOT_PRIVATE_KEY as `0x${string}`,
});Never hardcode private keys in source code. Use an encrypted keystore file with a passphrase, or load from environment variables.
Properties
botAddress
get botAddress(): AddressReturns the public address derived from the botPrivateKey provided in the constructor config. This is the address that must be registered (whitelisted) on the vault contract.
console.log(axon.botAddress);
// "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"Methods
pay()
async pay(input: PayInput): Promise<PaymentResult>Signs a payment intent and submits it to the Axon relayer. This is the primary method most bots will use.
The method handles the full lifecycle: constructing the EIP-712 typed data, signing it with the bot's private key, and POSTing the signed intent to the relayer's /v1/payments endpoint.
PayInput
| Parameter | Type | Required | Description |
|---|---|---|---|
| to | Address | Required | Recipient address. Can be an EOA or contract. |
| token | TokenInput | Required | Token for the payment. Accepts an address ('0x...'), Token enum (Token.USDC), or bare symbol string ('USDC'). |
| amount | AmountInput | Required | Payment amount. Accepts bigint (raw base units), number (human-readable, e.g. 5.2), or string ('5.2'). The SDK converts human-readable values using the token's decimals. |
| memo | string | Optional | Human-readable memo stored off-chain by the relayer. Max 1000 characters. Surfaced in the dashboard audit trail. |
| idempotencyKey | string | Optional | UUID for deduplication. If omitted, the SDK generates one automatically. Re-submitting the same key returns the original response. |
| resourceUrl | string | Optional | The URL being unlocked by this payment, used in HTTP 402 flows. |
| invoiceId | string | Optional | External invoice reference for reconciliation. Max 255 characters. |
| orderId | string | Optional | External order or job ID. Max 255 characters. |
| metadata | Record<string, string> | Optional | Arbitrary key-value pairs. Up to 10 keys, 500 characters per value. Stored off-chain. |
| deadline | bigint | Optional | Unix timestamp after which the intent expires. Defaults to current time + DEFAULT_DEADLINE_SECONDS (300s / 5 minutes). |
| ref | Hex | Optional | bytes32 on-chain reference. If omitted and a memo is provided, the SDK computes keccak256(memo) automatically. |
PaymentResult
interface PaymentResult {
requestId: string;
status: PaymentStatus;
txHash?: Hex;
pollUrl?: string;
estimatedResolutionMs?: number;
reason?: string;
}| Field | Description |
|---|---|
requestId | Unique identifier for this payment request. Use with poll() to check status. |
status | One of "approved", "pending_review", or "rejected". |
txHash | The on-chain transaction hash. Present when status is "approved". |
pollUrl | Relative URL for polling. Present when the payment requires async resolution. |
estimatedResolutionMs | Estimated time to resolution in milliseconds (typically ~30000 for AI scan path). |
reason | Human-readable explanation. Present when status is "rejected". |
Response Paths
The relayer resolves payments through one of three paths:
Fast path — small transaction, no policy triggers. Returns synchronously with txHash.
const result = await axon.pay({
to: '0xRecipient',
token: 'USDC',
amount: 5,
memo: 'API call payment',
});
// result.status === "approved"
// result.txHash === "0x..."AI scan path — amount or velocity exceeds threshold. The relayer runs 3-agent verification (~30s). If consensus is reached, returns txHash. If not, routes to human review.
// result.status === "approved" (consensus reached)
// or
// result.status === "pending_review" (no consensus)
// result.pollUrl === "/v1/payments/req_abc123"Human review path — requires owner approval via the dashboard. Returns pending_review with a pollUrl.
// result.status === "pending_review"
// result.estimatedResolutionMs === undefined (depends on human)Example
import { AxonClient } from '@axonfi/sdk';
const result = await axon.pay({
to: '0xServiceProvider',
token: 'USDC',
amount: 25,
memo: 'Monthly subscription payment',
invoiceId: 'inv-2026-001',
metadata: { plan: 'pro', period: '2026-02' },
});
if (result.status === 'approved') {
console.log('Confirmed:', result.txHash);
} else if (result.status === 'pending_review') {
const final = await axon.poll(result.requestId);
console.log('Resolved:', final.status);
}Advanced: raw bigint amounts. If you need full control, you can still pass bigint amounts in token base units. This skips the SDK's decimal conversion entirely.
import { Token } from '@axonfi/sdk';
const result = await axon.pay({
to: '0xServiceProvider',
token: Token.USDC,
amount: 25_000_000n, // 25 USDC in 6-decimal base units
});poll()
async poll(requestId: string): Promise<PaymentResult>Polls the relayer for the current status of a payment request. Calls GET /v1/payments/:requestId under the hood.
| Parameter | Type | Required | Description |
|---|---|---|---|
| requestId | string | Required | The requestId returned from pay(). |
Returns the same PaymentResult shape. The status field reflects the current state: "approved", "pending_review", or "rejected".
const status = await axon.poll('req_abc123');
if (status.status === 'approved') {
console.log('Transaction hash:', status.txHash);
} else if (status.status === 'rejected') {
console.log('Rejected:', status.reason);
}For long-running human review flows, use client.poll() in a loop with appropriate backoff. Poll every 5 seconds for
AI scan paths (~30s resolution) and every 30-60 seconds for human review paths.
getBalance()
async getBalance(token: Address): Promise<bigint>Returns the vault's balance for a given ERC-20 token. Reads via the relayer — no RPC endpoint needed.
import { USDC } from '@axonfi/sdk';
const balance = await axon.getBalance(USDC[8453]);
console.log('USDC balance:', balance); // e.g. 5000000n (5 USDC)getBalances()
async getBalances(tokens: Address[]): Promise<Record<Address, bigint>>Returns the vault's balances for multiple tokens in a single call. Returns a record mapping token address to balance.
const balances = await axon.getBalances([USDC[8453], WETH_ADDRESS]);
console.log('USDC:', balances[USDC[8453]]);isActive()
async isActive(): Promise<boolean>Returns whether this bot is registered and active in the vault.
const active = await axon.isActive();
console.log('Bot is active:', active);isPaused()
async isPaused(): Promise<boolean>Returns whether the vault is currently paused.
getVaultInfo()
async getVaultInfo(): Promise<VaultInfo>Returns high-level vault info including owner, operator, paused state, version, and whether intent tracking is enabled. All data is read via the relayer.
const info = await axon.getVaultInfo();
console.log('Owner:', info.owner);
console.log('Paused:', info.paused);canPayTo()
async canPayTo(destination: Address): Promise<DestinationCheckResult>Checks whether this bot can pay to a given destination address. The relayer checks the blacklist, global whitelist, and bot-specific whitelist.
const check = await axon.canPayTo('0xRecipient');
if (!check.allowed) {
console.log('Blocked:', check.reason);
}isProtocolApproved()
async isProtocolApproved(protocol: Address): Promise<boolean>Returns whether a protocol address is approved for executeProtocol() calls on this vault.
swap()
async swap(input: SwapInput): Promise<PaymentResult>Signs a swap intent and submits it to the relayer for in-vault token rebalancing. Tokens stay in the vault (no external recipient). The output token must be in the vault's rebalance token whitelist.
SwapInput
| Parameter | Type | Required | Description |
|---|---|---|---|
| toToken | TokenInput | Required | Desired output token — address, Token enum, or symbol string ('WETH'). Must be in the rebalance token whitelist. |
| minToAmount | AmountInput | Required | Minimum output amount (slippage floor). Accepts bigint, number, or string. |
| fromToken | TokenInput | Optional | Source token to swap from — address, Token enum, or symbol string. Relayer resolves if omitted. |
| maxFromAmount | AmountInput | Optional | Max input amount for the swap. Accepts bigint, number, or string. |
| memo | string | Optional | Human-readable description. Gets keccak256-hashed to ref. |
| idempotencyKey | string | Optional | UUID for deduplication. Auto-generated if omitted. |
| deadline | bigint | Optional | Intent expiry (defaults to 5 min). |
const result = await axon.swap({
toToken: 'WETH',
minToAmount: 0.001,
memo: 'Rebalance USDC to WETH',
});getRebalanceTokens()
async getRebalanceTokens(): Promise<RebalanceTokensResult>Returns the effective rebalance token whitelist for this vault. Use this before calling swap() to check which output tokens are allowed.
const { source, tokens, rebalanceTokenCount } = await axon.getRebalanceTokens();
// source: "default" (relayer defaults) or "on_chain" (owner-set)
// tokens: ["0xUSDC...", "0xWETH...", "0xUSDT..."]If the owner set tokens on-chain (rebalanceTokenCount > 0), those override the relayer defaults entirely.
isRebalanceTokenAllowed()
async isRebalanceTokenAllowed(token: Address): Promise<{ allowed: boolean; source: 'default' | 'on_chain' }>Check if a specific token is allowed for rebalancing (executeSwap output) on this vault.
const { allowed, source } = await axon.isRebalanceTokenAllowed(WETH);
if (!allowed) console.log('Cannot rebalance to WETH');signPayment()
async signPayment(intent: PaymentIntent): Promise<Hex>Signs a PaymentIntent using the bot's private key and returns the EIP-712 signature. This is a lower-level method for cases where you want to sign an intent without immediately submitting it to the relayer.
| Parameter | Type | Required | Description |
|---|---|---|---|
| intent | PaymentIntent | Required | The payment intent struct to sign. All fields are required. |
PaymentIntent
interface PaymentIntent {
bot: Address;
to: Address;
token: Address;
amount: bigint;
deadline: bigint;
ref: Hex; // bytes32
}import { encodeRef } from '@axonfi/sdk';
const intent: PaymentIntent = {
bot: axon.botAddress,
to: '0xRecipient',
token: USDC[8453],
amount: parseUnits('10', 6),
deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
ref: encodeRef('my-reference'),
};
const signature = await axon.signPayment(intent);
// signature: "0x..." (65-byte EIP-712 signature)Most bots should use pay() instead, which handles intent construction, signing, and submission in one call. Use
signPayment() only if you need to decouple signing from submission.
Keystore Utilities
The SDK exports two standalone functions for encrypting and decrypting bot private keys using the standard Ethereum V3 keystore format.
encryptKeystore()
import { encryptKeystore } from '@axonfi/sdk';
const keystore = await encryptKeystore('0xYourPrivateKey', 'my-strong-passphrase');
fs.writeFileSync('axon-bot-0xABCD1234.json', JSON.stringify(keystore, null, 2));Encrypts a raw private key into a V3 keystore JSON object using scrypt KDF and AES-128-CTR cipher. The resulting file is safe to store in cloud backups, password managers, or version control — it cannot be decrypted without the passphrase.
| Parameter | Type | Required | Description |
|---|---|---|---|
| privateKey | Hex | Required | The bot private key as a 0x-prefixed hex string. |
| passphrase | string | Required | Passphrase to encrypt the key with. Must not be empty. |
Returns a Promise<KeystoreV3> object that can be serialized to JSON.
decryptKeystore()
import { decryptKeystore } from '@axonfi/sdk';
const privateKey = await decryptKeystore(keystoreJson, process.env.BOT_PASSPHRASE!);Decrypts a V3 keystore back to a raw private key. Throws "Wrong passphrase: MAC mismatch" if the passphrase is incorrect.
| Parameter | Type | Required | Description |
|---|---|---|---|
| keystore | KeystoreV3 | string | Required | The V3 keystore as a parsed object or a JSON string. |
| passphrase | string | Required | The passphrase used during encryption. |
Returns a Promise<Hex> — the raw private key.
Is encryption required? No. Bot keys have limited blast radius in Axon — they can only sign expiring intents within your spending limits. The bot cannot withdraw funds or change vault config. A compromised key's damage is bounded by on-chain caps, and the owner can remove the bot instantly.
The keystore is a recommended safeguard against accidental exposure (clipboard leaks, git commits), not a hard security requirement. For best security, store the keystore file and passphrase separately (e.g. file on disk, passphrase in a secrets manager).
Types
All types are exported from the package root:
import type {
AxonClientConfig,
PayInput,
SwapInput,
PaymentResult,
PaymentIntent,
SwapIntent,
PaymentStatus,
BotConfig,
SpendingLimit,
OperatorCeilings,
RebalanceTokensResult,
KeystoreV3,
TokenInput,
AmountInput,
} from '@axonfi/sdk';TokenInput
type TokenInput = Address | Token | KnownTokenSymbol;Accepts any way to identify a token: a raw 0x-prefixed address, a Token enum value, or a bare symbol string like 'USDC' or 'WETH'.
AmountInput
type AmountInput = bigint | number | string;Accepts amounts in any format:
bigint— raw base units (e.g.5_000_000nfor 5 USDC). Passed through as-is.number— human-readable (e.g.5.2). SDK converts using token decimals.string— human-readable string (e.g.'5.2'). Recommended for computed values to avoid float precision issues.
PaymentStatus
type PaymentStatus = 'approved' | 'pending_review' | 'rejected';OperatorCeilings
interface OperatorCeilings {
maxPerTxAmount: bigint;
maxBotDailyLimit: bigint;
maxOperatorBots: bigint;
vaultDailyAggregate: bigint;
minAiTriggerFloor: bigint;
}Operator ceilings are set by the vault owner and constrain what an operator (hot wallet) can configure. They are readable on-chain but not directly used by most bot integrations.
Amount Utilities
parseAmount()
import { parseAmount } from '@axonfi/sdk';
parseAmount(5.2, 'USDC') // 5_200_000n
parseAmount('5.2', Token.USDC) // 5_200_000n
parseAmount(0.001, 'WETH') // 1_000_000_000_000_000n
parseAmount(5_000_000n, 'USDC') // 5_000_000n (passthrough)Converts human-friendly amounts to raw base units. If the input is already a bigint, it is passed through unchanged. Numbers and strings are converted using viem.parseUnits with the token's decimal count from the KNOWN_TOKENS registry.
Throws if the amount has more decimal places than the token supports (e.g. '5.1234567' for USDC which has 6 decimals).
Use string amounts for computed values (e.g. '0.3' instead of 0.1 + 0.2) to avoid JavaScript floating-point imprecision.
resolveTokenDecimals()
import { resolveTokenDecimals } from '@axonfi/sdk';
resolveTokenDecimals('USDC') // 6
resolveTokenDecimals(Token.WETH) // 18
resolveTokenDecimals('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913') // 6 (reverse lookup)Returns the number of decimals for a known token. Accepts a symbol string, Token enum, or known address.
Constants
import { USDC, WINDOW, SUPPORTED_CHAIN_IDS, DEFAULT_DEADLINE_SECONDS } from '@axonfi/sdk';| Constant | Type | Description |
|---|---|---|
USDC | Record<number, Address> | Canonical USDC addresses keyed by chain ID. |
WINDOW | object | Pre-defined time windows: ONE_HOUR, ONE_DAY, ONE_WEEK, THIRTY_DAYS. Values in seconds. |
SUPPORTED_CHAIN_IDS | number[] | Array of chain IDs supported by Axon (Base, Base Sepolia, Arbitrum, Arbitrum Sepolia). |
DEFAULT_DEADLINE_SECONDS | number | Default intent deadline: 300 (5 minutes). |
ABIs
The SDK exports typed ABIs for direct contract interaction via viem:
import { AxonVaultAbi, AxonVaultFactoryAbi, AxonRegistryAbi } from '@axonfi/sdk';These are useful if you need to read vault state or call contract functions directly (e.g. for dashboard or tooling integrations). Most bot use cases do not need these.