x402 quickstart — pay each invoke from a Base USDC wallet

Target reading time: 60 seconds. Target time-to-first-paid-invoke: 3 minutes. Spec: v1.1 §4.

Four commands take you from npm install to your agent settling USDC on Base for every paid capability invoke. No Stripe top-ups, no human in the loop. The SDK never sees your private key.

1. Install

npm install @jecpdev/sdk ethers

ethers is a peer dependency — the SDK does not ship it (browser bundles stay lean). v6 or newer is required.

2. Set the env var

export AGENT_BASE_KEY=0x...   # 32-byte private key for an EOA on Base

This is the wallet that will pay for invokes. Keep it isolated to your agent — don't reuse a personal wallet.

3. Top up the wallet

Move USDC to that address on Base mainnet (chain id 8453) or Base Sepolia (84532). Two paths:

A typical x402 invoke is $0.20-$1.00, so $5 of USDC is plenty for testing.

4. Code (10 lines)

import { JecpClient, walletFromEnv } from '@jecpdev/sdk';

const jecp = new JecpClient({
  agentId: process.env.JECP_AGENT_ID!,
  apiKey:  process.env.JECP_API_KEY!,
  payment: {
    mode: 'x402',
    signer: walletFromEnv(),       // reads AGENT_BASE_KEY
    maxPerCallUsdc: 1_000_000n,    // safety cap: $1 max per invoke
  },
});

const r = await jecp.invoke('jobdonebot/bg-remover-pro', 'remove', {
  image_url: 'https://example.com/cat.png',
});

Verify

console.log(r.output);          // capability result
console.log(r.payment?.txHash); // 0x... — settlement tx on Base
console.log(r.payment?.amount_usd); // 0.20

Look up the tx on https://basescan.org/tx/{txHash} to confirm the on-chain 85/10/5 revenue split landed in a single block.

Safety caps (recommended in production)

An autonomous agent that can be tricked into draining its wallet is a CVE. The SDK enforces three caps before signing, so a compromised facilitator or prompt-injected agent cannot extract a signature for a too-large amount.

payment: {
  mode: 'x402',
  signer: walletFromEnv(),
  maxPerCallUsdc: 1_000_000n,    // refuse to sign if 402 asks for > $1
  maxPerHourUsdc: 10_000_000n,   // refuse if rolling 1h sum + ask > $10
  maxGasRatio: 0.05,             // refuse if gas > 5% of invoke amount
}

Cap breaches throw typed errors with nextAction hints:

ErrornextAction.typeWhat to do
X402AmountCapExceededErrorraise_capThe Hub asked for more than your per-call cap. Audit the capability's pricing or bump the cap.
X402HourlyCapExceededErrorreview_intentYour agent ran hot — pause it and inspect call patterns.
X402GasRatioExceededErrorcheck_gasBase congested. Wait, or raise maxGasRatio if you accept higher overhead.

These caps are not a substitute for Hub-side mandate budgets — they are the inner-most ring of defense, paired with mandate.budget_usdc.

Default mode is auto — backward compatible

mode: 'x402' strictly refuses to fall back to Stripe wallet path. If you want both:

payment: { mode: 'auto', signer: walletFromEnv() }

auto (default) tries x402 first when the capability supports it, otherwise surfaces the typed 402 error so you can drive a Stripe Checkout via err.nextAction.

Error handling

import {
  X402PaymentInvalidError,
  X402SettlementTimeoutError,
  X402FacilitatorUnreachableError,
  X402SettlementReusedError,
  X402AmountCapExceededError,
} from '@jecpdev/sdk';

try {
  const r = await jecp.invoke(/* ... */);
} catch (e) {
  if (e instanceof X402PaymentInvalidError) {
    // subcause: signature_invalid | amount_mismatch | nonce_reused | expired
    console.log('facilitator rejected:', e.subcause, e.nextAction?.hint);
  } else if (e instanceof X402SettlementTimeoutError) {
    // retryable — wait, regenerate nonce, retry
  }
  // ... other branches
}

The full error catalog is in spec/03-error-catalog.md.

Performance characteristics

The 402-then-X-Payment retry adds 200-300ms to the first invoke wall clock (one extra round-trip + EIP-712 signing). Subsequent invokes in the same JecpClient re-use the signer adapter — there's no warm-up cost.

If you'd rather avoid the round-trip, the Hub publishes per-action pricing in the catalog manifest; pre-sign by reading JecpClient.estimateCost() and constructing your own EIP-3009 authorization.

Running the live example

git clone https://github.com/jecpdev/jecp-sdk-typescript
cd jecp-sdk-typescript
npm install
export AGENT_BASE_KEY=0x...
export JECP_AGENT_ID=jdb_ag_...
export JECP_API_KEY=jdb_ak_...
npx tsx examples/05-x402-invoke.ts

What's NOT in this quickstart

Single-block atomic, not same-tx. The x402.org facilitator pulls USDC and the Splitter contract distributes 85/10/5 in two transactions in the same block (~2s apart). Treat them as atomic for accounting; do not assume single-tx semantics.