AI Agent Payments Guide

Recurring payments for AI agents

AuthOnce has native ERC-1271 support for smart-contract agent wallets, alongside standard EIP-2612 permit support for EOA agents, on Base Network.

Developer guide. This page assumes familiarity with EVM smart contracts, ethers.js or viem, and AI agent wallet architectures. For a business-level overview, read our AI agent payments blog post first.

Status: ERC-1271 subscriptions are not yet live in production. createSubscription() works today and records a smart-contract-wallet subscription on-chain. But the production keeper bot does not yet execute recurring pulls for ERC-1271 subscriptions — that support is still being built. A subscription created via this path will not be billed automatically until this ships. If you're integrating an AI agent for production use now, contact us first so we can tell you the real status.

Why AI agents need AuthOnce

AI agents increasingly need to pay for services autonomously — API access, data feeds, compute, tooling. Current payment infrastructure is not designed for autonomous actors:

❌ Legacy billing
  • Requires human to enter card details
  • OAuth flows need browser + human
  • API keys are not payment primitives
  • No programmable spending limits
  • Chargebacks and disputes possible
✅ AuthOnce
  • EOA agents: sign a permit once. Smart-contract agent wallets: sign a fresh authorisation each cycle
  • No manual re-entry of payment details after setup
  • Payment IS the access credential
  • Guardian sets hard spending limits
  • On-chain — cryptographically final

How it works

🤖

Agent wallet creates the subscription

The AI agent's smart contract wallet calls createSubscription(), registering the token, amount, interval, and grace period on-chain. This one call does not authorise any future payment by itself.

🔒

Vault stores the subscription on-chain

The subscription is registered in the SubscriptionVault contract. The agent's vault address, token, amount, interval, and grace period are immutably stored.

✍️

Agent wallet signs a fresh authorisation each cycle

Unlike an EOA's one-time permit, a smart-contract wallet must produce a new EIP-712 signature — bound to that specific subscription, amount, pull number, and deadline — for every single billing cycle. Something has to generate that signature autonomously each cycle for this to run without a human; AuthOnce does not hold a key that can do this on the agent's behalf.

⚡

Keeper submits the pull

The keeper bot calls executePull() with that cycle's signature. The contract validates it via isValidSignature() and transfers USDC from the agent's vault to the merchant.

🛡️

Guardian retains override control

The human operator (guardian) can pause or cancel any subscription at any time. The agent cannot exceed its pre-authorised spending without guardian approval.

ERC-1271 & EIP-2612

ERC-1271 — Smart contract signature validation

ERC-1271 is the standard for smart contract wallets to validate signatures. Instead of a private key signing a message (as with EOAs), the contract exposes an isValidSignature(bytes32 hash, bytes signature) function. AuthOnce's vault contract calls this during executePull() to verify the agent authorised the subscription.

This means any AI agent wallet that implements ERC-1271 — including Safe multisigs, custom agent wallets, and EIP-4337 smart accounts — is natively compatible with AuthOnce.

EIP-2612 — Permit-based token approval

EIP-2612 permits allow token approvals via off-chain signatures rather than on-chain transactions. For AI agents:

EOA agents work too. If your agent uses a regular private key (EOA), the ERC-1271 check is skipped entirely — no per-cycle signature needed. The EIP-2612 permit, bundled into the same createSubscription() call described above, is what authorises the keeper to pull USDC on schedule going forward.

Agent wallet setup

Your agent needs:

  1. An EVM wallet on Base Network — EOA (private key) or smart contract wallet (ERC-1271)
  2. USDC balance — at least 1× the subscription amount per billing cycle. Keep 2–3× as buffer.
  3. ETH for gas — only needed for the initial createSubscription() call. The keeper pays gas for all subsequent executePull() calls.

Vault funding model. The agent's vault is funded with exactly 1× the subscription amount per billing cycle. The vault is not a pooled balance — each subscription cycle requires the agent to maintain sufficient USDC in the vault address. AuthOnce's grace period gives the agent time to top up if a payment fails.

Creating a subscription

Call createSubscription() on the SubscriptionVault contract. The key parameters for agent integrations:

ParameterTypeNotes
merchantaddressThe merchant's registered wallet address
safeVaultaddressMust equal msg.sender. The agent's own address.
tokenaddressUSDC on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
amountuint256Amount in token decimals (USDC has 6 decimals)
intervaluint80 = Weekly, 1 = Monthly, 2 = Yearly
guardianaddressHuman operator address. Can pause or cancel. Set to address(0) for no guardian.
gracePeriodDays_uint2561–30. Days before expiry if payment fails.
dataVaultId_bytes32Set to bytes32(0) — reserved for DataOnce phase.

Spending limits

AuthOnce's vault architecture enforces spending limits at the protocol level:

This means the maximum the protocol can ever charge the agent in a single cycle is the amount encoded at subscription creation. This is a hard on-chain guarantee, not a policy.

Guardian control

The guardian field gives a human operator override capability over the agent's subscriptions:

ActionWho can trigger
Cancel subscriptionAgent wallet OR guardian
Pause subscriptionGuardian only
Resume subscriptionGuardian only
Change vault addressAgent wallet only (must equal msg.sender)

Set the guardian to your human operator's address when creating the subscription. This gives you a kill switch if the agent behaves unexpectedly.

ethers.js example

Full integration for an EOA agent wallet:

import { ethers } from "ethers";

const VAULT_ADDRESS  = "0x..."; // SubscriptionVault on Base Mainnet
const USDC_ADDRESS   = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const MERCHANT_ADDR  = "0x..."; // Merchant registered address
const GUARDIAN_ADDR  = "0x..."; // Your human operator wallet

const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
const agentWallet = new ethers.Wallet(process.env.AGENT_PRIVATE_KEY, provider);

const VAULT_ABI = [
  "function createSubscription(address merchant, address safeVault, address token, uint256 amount, uint256 introAmount, uint256 introPulls, uint8 interval, address guardian, uint256 trialDays, uint256 gracePeriodDays_, bytes32 dataVaultId_) returns (uint256)",
];
const ERC20_ABI = ["function approve(address spender, uint256 amount) returns (bool)"];

const vault = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, agentWallet);
const usdc  = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, agentWallet);

// 1. Approve vault to pull USDC
const MONTHLY_AMOUNT = ethers.parseUnits("49.00", 6); // $49.00 USDC
await (await usdc.approve(VAULT_ADDRESS, MONTHLY_AMOUNT)).wait();

// 2. Create subscription
const tx = await vault.createSubscription(
  MERCHANT_ADDR,
  agentWallet.address,   // safeVault == msg.sender
  USDC_ADDRESS,
  MONTHLY_AMOUNT,
  0n,                    // introAmount
  0n,                    // introPulls
  1,                     // Monthly
  GUARDIAN_ADDR,
  0n,                    // trialDays
  7n,                    // gracePeriodDays
  ethers.ZeroHash        // dataVaultId
);
const receipt = await tx.wait();
console.log("Subscription created. Tx:", receipt.hash);
// From here, the keeper handles all future pulls automatically.

viem example

import { createWalletClient, http, parseUnits, zeroHash } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);
const client  = createWalletClient({ account, chain: base, transport: http() });

const MONTHLY_AMOUNT = parseUnits("49.00", 6);

// 1. Approve
await client.writeContract({
  address: USDC_ADDRESS,
  abi: [{ name: "approve", type: "function", inputs: [{ name: "spender", type: "address"}, { name: "amount", type: "uint256"}], outputs: [{ type: "bool"}] }],
  functionName: "approve",
  args: [VAULT_ADDRESS, MONTHLY_AMOUNT],
});

// 2. Create subscription
await client.writeContract({
  address: VAULT_ADDRESS,
  abi: VAULT_ABI,
  functionName: "createSubscription",
  args: [MERCHANT_ADDR, account.address, USDC_ADDRESS, MONTHLY_AMOUNT, 0n, 0n, 1, GUARDIAN_ADDR, 0n, 7n, zeroHash()],
});

ERC-1271 verification (smart contract agents)

If your agent is a smart contract wallet (Safe, EIP-4337 account, custom), the contract must implement:

// Solidity — implement in your agent contract
bytes4 constant MAGIC_VALUE = 0x1626ba7e;

function isValidSignature(
  bytes32 hash,
  bytes memory signature
) external view returns (bytes4) {
  // Validate the signature against your agent's signing key
  // Return MAGIC_VALUE if valid, 0xffffffff if not
  address signer = recoverSigner(hash, signature);
  if (authorisedSigners[signer]) {
    return MAGIC_VALUE;
  }
  return 0xffffffff;
}

AuthOnce's executePull() calls isValidSignature() on the subscriber vault if it detects a contract at that address. Return 0x1626ba7e to approve, anything else to reject.

Contract addresses

ContractNetworkAddress
SubscriptionVault v7Base Sepolia (testnet)0xeb068B47731261F7B4A5ae8535686D67D7f72321
MerchantRegistry v4Base Sepolia (testnet)0xAE681E431c353f5930dDFfBC74037d3f2afE3264
SubscriptionVaultBase MainnetNot yet deployed
USDCBase Mainnet0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
USDCBase Sepolia0x036CbD53842c5426634e7929541eC2318f3dCF7e

Key ABI methods

// Core methods you need
[
  "function createSubscription(address merchant, address safeVault, address token, uint256 amount, uint256 introAmount, uint256 introPulls, uint8 interval, address guardian, uint256 trialDays, uint256 gracePeriodDays_, bytes32 dataVaultId_) returns (uint256 id)",
  "function cancelSubscription(uint256 id)",
  "function pauseSubscription(uint256 id, string reason)",
  "function resumeSubscription(uint256 id)",
  "function nextPullDue(uint256 id) view returns (uint256)",
  "function getSubscription(uint256 id) view returns (tuple(address owner, address merchant, address safeVault, address token, uint256 amount, uint8 interval, uint8 status, uint256 lastPulledAt, uint256 gracePeriodDays, address guardian))",

  // Events
  "event SubscriptionCreated(uint256 indexed id, address indexed owner, address indexed merchant, address safeVault, address token, uint256 amount, uint256 introAmount, uint256 introPulls, uint8 interval, address guardian, uint256 trialEndsAt, uint256 gracePeriodDays, bool isContractVault)",
  "event PaymentExecuted(uint256 indexed id, address indexed merchant, address indexed token, uint256 amount, uint256 merchantReceived, uint256 protocolFee)",
  "event SubscriptionCancelled(uint256 indexed id, address indexed cancelledBy)"
]

Full ABI available on Basescan. The contracts are verified. Search for the SubscriptionVault address on sepolia.basescan.org to read the full ABI and source code.

Building an AI agent on Base?

Get in touch — we're actively supporting early AI agent integrations and can assist with custom configurations.

Contact us → Web3 guide