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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| walletClient | WalletClient | Required | A viem WalletClient instance. Can be backed by a local account, hardware wallet, or any EIP-1193 provider. |
| vaultAddress | Address | Required | The address of the AxonVault contract. Used in the EIP-712 domain separator. |
| chainId | number | Required | The chain ID. Used in the EIP-712 domain separator to prevent cross-chain replay. |
| intent | PaymentIntent | Required | The 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' },
];
}| Field | Description |
|---|---|
bot | The signing bot's address. Must be whitelisted on the vault. |
to | Payment recipient address. |
token | ERC-20 token contract address. |
amount | Amount in the token's smallest unit (e.g. 6 decimals for USDC). |
deadline | Unix timestamp. The intent is invalid after this time. Default: 5 minutes from now. |
ref | A 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| memo | string | Required | A 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.