Skip to main content

Make Payments with Ethers.js

Make payments to Armory-protected APIs using Ethers.js wallets. hooks are lifecycle callbacks. extensions are protocol fields on x402 payloads/challenges. They work together but are separate. When payment verification fails on retry (402), Armory surfaces server detail (for example insufficient_funds) in the error path.

Basic Payment

import { armoryPay } from '@armory-sh/client-ethers';
import { ethers } from 'ethers';

const signer = new ethers.Wallet('0x...');

const result = await armoryPay(
  { signer },
  'https://api.example.com/data',
  'base',
  'usdc'
);

console.log(result.data);

Armory object workflow

import { createArmory } from '@armory-sh/client-ethers';
import { ethers } from 'ethers';

const signer = new ethers.Wallet('0x...');

const armory = createArmory({
  wallet: signer,
  chains: 'base',
  tokens: 'usdc',
  debug: true,
});

const premium = await armory.post('https://api.example.com/premium', { tier: 'pro' });
const info = await armory.call('https://api.example.com/data');

console.log(premium.data, info.data);
createArmory exposes .get, .post, .put, .delete, .patch, .pay, and .call. When a 402 response arrives, it selects from accepts[]. Without hooks it uses the first compatible option; add @armory-sh/client-hooks to apply chain/token/cheapest preferences. Use .pay(url, { method: 'PATCH', body }) when you need to override the method and .call(url) if you just want the default GET request with payment.

With Provider

import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://...');
const signer = new ethers.Wallet('0x...', provider);

const result = await armoryPay(
  { signer },
  'https://api.example.com/data',
  'base',
  'usdc'
);