Skip to main content

Make Payments with Viem

Make payments to Armory-protected APIs using Viem 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 thrown error.

Basic Payment

import { armoryPay } from '@armory-sh/client-viem';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');

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

console.log(result.data);

Prefer the Armory object (method-based API)

For multi-call flows the new createArmory object bundles the payment client with method helpers and automatic server option selection.
import { createArmory } from '@armory-sh/client-viem';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');

const armory = createArmory({
  wallet: { account },
  // Restrict the object to Base + USDC if you want to avoid extra lookups
  chains: 'base',
  tokens: 'usdc',
  debug: true,
});

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

console.log(premium.data, info.data);
createArmory handles 402 responses from PAYMENT-REQUIRED and selects from accepts[]. Without hooks it uses the first compatible option. Add @armory-sh/client-hooks to apply chain/token/cheapest preference logic. The object exposes .get, .post, .put, .delete, .patch, .pay, and a .call shorthand for GET requests. Use armory.pay(url, { method: 'PATCH', body }) when you need to override the HTTP method; call .call for a simple GET.

With Custom Amount

const result = await armoryPay(
  { account },
  'https://api.example.com/premium',
  'base',
  'usdc',
  { amount: '5.0' }
);

Using Public Client

import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

const publicClient = createPublicClient({
  chain: base,
  transport: http()
});

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

With Extension Hooks

For servers that require protocol extensions (like Sign-In-With-X):
import { createX402Client } from '@armory-sh/client-viem';
import { createSIWxHook, createPaymentIdHook } from '@armory-sh/extensions';
import { PaymentPreference, Logger } from '@armory-sh/client-hooks';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');

const client = createX402Client({
  wallet: { type: 'account', account },
  hooks: [
    // Extension hooks
    createSIWxHook({
      domain: 'example.com',
      statement: 'Sign in to access premium content'
    }),
    createPaymentIdHook(),

    // Optional preference hooks
    PaymentPreference.chain(['base', 'polygon', 'skale']),
    PaymentPreference.token(['USDT', 'USDC', 'WBTC']),
    PaymentPreference.cheapest(),
    Logger.console(),
  ]
});

// Hooks automatically add extensions when server requests them
const response = await client.fetch('https://api.example.com/protected');
const data = await response.json();

Multiple Requests

Reuse the client for multiple requests:
const client = createX402Client({
  wallet: { type: 'account', account },
  hooks: [createSIWxHook()]
});

// First request - may trigger SIWX signing
const response1 = await client.fetch('https://api.example.com/data1');

// Subsequent requests - hooks execute as needed
const response2 = await client.fetch('https://api.example.com/data2');

Custom Extensions

Create custom hooks for your own extensions:
import { createCustomHook } from '@armory-sh/extensions';

const myExtensionHook = createCustomHook({
  key: 'my-extension',
  handler: async (context) => {
    if (context.payload) {
      context.payload.extensions = {
        ...(context.payload.extensions ?? {}),
        'my-extension': { timestamp: Date.now() }
      };
    }
  },
  priority: 75
});

const client = createX402Client({
  wallet: { type: 'account', account },
  hooks: [myExtensionHook]
});