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.

ParameterTypeRequiredDescription
walletClientWalletClientRequiredA viem WalletClient instance. Can be backed by a local account, hardware wallet, or any EIP-1193 provider.
vaultAddressAddressRequiredThe address of the AxonVault contract. Used in the EIP-712 domain separator.
chainIdnumberRequiredThe chain ID. Used in the EIP-712 domain separator to prevent cross-chain replay.
intentPaymentIntentRequiredThe payment intent struct to sign.

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.

ParameterTypeRequiredDescription
memostringRequiredA string to encode. It is always hashed with keccak256 to produce a deterministic bytes32 value.

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.