Signing Helpers

Low-level EIP-712 signing utilities for custom integrations.

Signing Helpers

The SDK exports standalone functions for EIP-712 signing and reference encoding. These are useful when you need fine-grained control over the signing process -- for example, when integrating with a custom wallet provider or building a framework plugin.

Most bots should use AxonClient.pay() which handles signing automatically. Use these helpers only for custom integrations or when you need to separate signing from submission.

signPayment()

import { signPayment } from '@axonfi/sdk';
 
const signature = await signPayment(walletClient, vaultAddress, chainId, intent);

Signs a PaymentIntent with any viem WalletClient. This is the function AxonClient uses internally, exposed for cases where you manage your own wallet client.

Returns a Promise<Hex> containing the 65-byte EIP-712 signature.

EIP-712 Domain

The domain separator used for all Axon payment intents:

{
  name: "AxonVault",
  version: "1",
  chainId: chainId,
  verifyingContract: vaultAddress,
}

The vault contract reconstructs this same domain on-chain during signature verification. The chainId field prevents a signature created for Base from being replayed on Arbitrum.

PaymentIntent Type

The EIP-712 typed data structure:

{
  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' },
  ];
}
FieldDescription
botThe signing bot's address. Must be whitelisted on the vault.
toPayment recipient address.
tokenERC-20 token contract address.
amountAmount in the token's smallest unit (e.g. 6 decimals for USDC).
deadlineUnix timestamp. The intent is invalid after this time. Default: 5 minutes from now.
refA bytes32 value linking the on-chain transaction to off-chain records.

Example: Custom Wallet Client

import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import { signPayment, decryptKeystore, encodeRef, Chain, resolveToken, Token } from '@axonfi/sdk';
import fs from 'fs';
 
// Load key from encrypted keystore (recommended) or env var
const keystore = fs.readFileSync(process.env.AXON_BOT_KEYSTORE_PATH!, 'utf8');
const privateKey = await decryptKeystore(keystore, process.env.AXON_BOT_PASSPHRASE!);
 
const account = privateKeyToAccount(privateKey);
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http('https://mainnet.base.org'),
});
 
const intent = {
  bot: account.address,
  to: '0xRecipient' as `0x${string}`,
  token: resolveToken(Token.USDC, Chain.Base),
  amount: 10_000_000n, // 10 USDC
  deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
  ref: encodeRef('custom-ref-001'),
};
 
const signature = await signPayment(walletClient, '0xYourVaultAddress', Chain.Base, intent);

encodeRef()

import { encodeRef } from '@axonfi/sdk';
 
const ref: Hex = encodeRef('invoice-2026-001');

Converts a human-readable string into a bytes32 hex value suitable for the ref field in a PaymentIntent. The string is always hashed with keccak256, regardless of length.

Returns a Hex string (0x-prefixed, 66 characters total) — the keccak256 hash of the UTF-8 encoded memo.

Encoding

All strings are hashed with keccak256. The full original text is stored off-chain by the relayer (in the memo field in PostgreSQL) and linked to the on-chain transaction via the hash.

encodeRef('inv-001');
// "0x8a35..." (keccak256 hash)
 
encodeRef('Payment for 500 API calls to image generation service in February');
// "0xa1b2c3..." (keccak256 hash)

When using AxonClient.pay(), you can pass a memo string and omit ref. The SDK will call encodeRef(memo) automatically and set the ref field for you.


Standalone Helper Functions

The SDK also exports read-only helper functions for querying vault state directly:

import { getBotConfig, isBotActive, deployVault } from '@axonfi/sdk';

getBotConfig()

async function getBotConfig(publicClient: PublicClient, vaultAddress: Address, botAddress: Address): Promise<BotConfig>;

Reads a bot's configuration from the vault contract. This is the standalone equivalent of AxonClient.getBotConfig(), useful when you do not have a full client instance.

isBotActive()

async function isBotActive(publicClient: PublicClient, vaultAddress: Address, botAddress: Address): Promise<boolean>;

Returns true if the bot address is currently whitelisted and active on the vault.

deployVault()

async function deployVault(
  walletClient: WalletClient,
  publicClient: PublicClient,
  factoryAddress: Address,
): Promise<Address>;

Deploys a new AxonVault via the factory contract. The walletClient account becomes the vault owner. Returns the deployed vault address after waiting for the transaction receipt. This is an owner-facing function, not typically called by bots.