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

ParameterTypeRequiredDescription
vaultAddressAddressRequiredThe on-chain address of the AxonVault this client operates against.
chainIdnumberRequiredThe EVM chain ID where the vault is deployed (e.g. 8453 for Base, 42161 for Arbitrum).
botPrivateKeyHexOptionalThe 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(): Address

Returns 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

ParameterTypeRequiredDescription
toAddressRequiredRecipient address. Can be an EOA or contract.
tokenTokenInputRequiredToken for the payment. Accepts an address ('0x...'), Token enum (Token.USDC), or bare symbol string ('USDC').
amountAmountInputRequiredPayment 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.
memostringOptionalHuman-readable memo stored off-chain by the relayer. Max 1000 characters. Surfaced in the dashboard audit trail.
idempotencyKeystringOptionalUUID for deduplication. If omitted, the SDK generates one automatically. Re-submitting the same key returns the original response.
resourceUrlstringOptionalThe URL being unlocked by this payment, used in HTTP 402 flows.
invoiceIdstringOptionalExternal invoice reference for reconciliation. Max 255 characters.
orderIdstringOptionalExternal order or job ID. Max 255 characters.
metadataRecord<string, string>OptionalArbitrary key-value pairs. Up to 10 keys, 500 characters per value. Stored off-chain.
deadlinebigintOptionalUnix timestamp after which the intent expires. Defaults to current time + DEFAULT_DEADLINE_SECONDS (300s / 5 minutes).
refHexOptionalbytes32 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;
}
FieldDescription
requestIdUnique identifier for this payment request. Use with poll() to check status.
statusOne of "approved", "pending_review", or "rejected".
txHashThe on-chain transaction hash. Present when status is "approved".
pollUrlRelative URL for polling. Present when the payment requires async resolution.
estimatedResolutionMsEstimated time to resolution in milliseconds (typically ~30000 for AI scan path).
reasonHuman-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.

ParameterTypeRequiredDescription
requestIdstringRequiredThe 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

ParameterTypeRequiredDescription
toTokenTokenInputRequiredDesired output token — address, Token enum, or symbol string ('WETH'). Must be in the rebalance token whitelist.
minToAmountAmountInputRequiredMinimum output amount (slippage floor). Accepts bigint, number, or string.
fromTokenTokenInputOptionalSource token to swap from — address, Token enum, or symbol string. Relayer resolves if omitted.
maxFromAmountAmountInputOptionalMax input amount for the swap. Accepts bigint, number, or string.
memostringOptionalHuman-readable description. Gets keccak256-hashed to ref.
idempotencyKeystringOptionalUUID for deduplication. Auto-generated if omitted.
deadlinebigintOptionalIntent 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.

ParameterTypeRequiredDescription
intentPaymentIntentRequiredThe 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.

ParameterTypeRequiredDescription
privateKeyHexRequiredThe bot private key as a 0x-prefixed hex string.
passphrasestringRequiredPassphrase 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.

ParameterTypeRequiredDescription
keystoreKeystoreV3 | stringRequiredThe V3 keystore as a parsed object or a JSON string.
passphrasestringRequiredThe 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_000n for 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';
ConstantTypeDescription
USDCRecord<number, Address>Canonical USDC addresses keyed by chain ID.
WINDOWobjectPre-defined time windows: ONE_HOUR, ONE_DAY, ONE_WEEK, THIRTY_DAYS. Values in seconds.
SUPPORTED_CHAIN_IDSnumber[]Array of chain IDs supported by Axon (Base, Base Sepolia, Arbitrum, Arbitrum Sepolia).
DEFAULT_DEADLINE_SECONDSnumberDefault 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.