npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

paybot-sdk

v0.5.0

Published

PayBot SDK - USDC payments for bots via x402 protocol

Readme

paybot-sdk

USDC and EURC payments for bots via the x402 protocol. One dependency (viem), typed everything.

Key Features

  • One dependency (viem), fully typed
  • Simple API — register your bot and make payments in 2 lines of code
  • Network support — Base, Optimism, Arbitrum One, Polygon PoS mainnets + Base Sepolia testnet (EIP155)
  • Mock mode for testing without real transactions
  • MCP integration — works with AI agent frameworks via paybot-mcp
  • Self-hostable facilitator service

Architecture

PayBotClient → Facilitator (x402) → On-chain USDC (EIP-3009)

The SDK wraps payment logic for bots, handling registration, payment execution, and network configuration. Developers can use the hosted facilitator at api.paybotcore.com or run their own.

Install

npm install paybot-sdk

Quick Start

import { PayBotClient } from 'paybot-sdk';

const client = new PayBotClient({
  apiKey: 'pb_test_...',
  botId: 'my-bot',
  facilitatorUrl: 'https://api.paybotcore.com',
});

// Register your bot
await client.register();

// Make a payment
const result = await client.pay({
  resource: 'https://api.example.com/data',
  amount: '0.01',
  payTo: '0x1234...abcd',
});

console.log(result.success, result.txHash);

x402 Auto-Handler

Automatically pay for HTTP 402 responses:

import { createX402Handler } from 'paybot-sdk';

const handler = createX402Handler({
  apiKey: 'pb_test_...',
  botId: 'my-bot',
  maxAutoPay: '1.00', // Max USD per auto-payment
});

// If the server returns 402, PayBot pays and retries automatically
const response = await handler.fetch('https://api.example.com/paid-endpoint');
const data = await response.json();

When the server places a payment in its approval band (a borderline amount that needs a human decision), the auto-handler does not block and does not throw — it returns a 202 response carrying the pending result, so your agent stays responsive:

const response = await handler.fetch('https://api.example.com/paid-endpoint');
if (response.status === 202) {
  const pending = await response.json(); // { status: 'pending_approval', approvalId, … }
  // Opt-in: wait for the human to decide (or carry on and check back later).
  const final = await handler.client.waitForApproval(pending.approvalId);
}

Pending vs. over-ceiling — two distinct paths. The client-side maxAutoPay ceiling still throws (your bot saying "I will never auto-pay this much"). A server-side pending_approval is a returned result (the server saying "a human should look at this"). They never collapse into one.

Human-in-the-Loop Approvals (pending payments)

When PayBotFin's policy places a payment in the approval band, pay() returns a non-throwing pending result instead of settling — the money has not moved, but the payment is paused awaiting a human, not failed:

const result = await client.pay({ resource, amount: '500', payTo });

if (result.status === 'pending_approval') {
  // success is false (money didn't move) but this is a PAUSE, not a failure.
  console.log('Awaiting approval:', result.approvalId, 'expires', result.expiresAt);

  // Opt-in: block until a human approves/denies, or the request times out.
  const final = await client.waitForApproval(result.approvalId!, {
    timeoutMs: 15 * 60_000, // default: 15 min (aligned to the server TTL)
    intervalMs: 3_000,      // default: 3 s poll cadence
  });

  if (final.success) {
    console.log('Approved + settled:', final.txHash);
  } else if (final.status === 'denied') {
    console.log('Denied or expired:', final.error);
  } else if (final.status === 'pending_approval') {
    // Local timeout — the SERVER record is still live; the SDK timing out never
    // cancels the approval. You may call waitForApproval(...) again later.
  }
}

Safe degradation: a consumer that only reads result.success keeps working unchanged — a pending payment looks like success: false ("not done yet"), with no new crash path. waitForApproval() itself never throws: poll errors and local timeouts are returned as PaymentResults, not exceptions.

Real Payments (EIP-3009)

Pass a wallet private key to sign actual on-chain USDC transfers:

const client = new PayBotClient({
  apiKey: 'pb_...',
  botId: 'my-bot',
  walletPrivateKey: '0x...', // Signs EIP-3009 TransferWithAuthorization
});

Trust Levels

PayBot enforces progressive trust levels that govern what your bot can do:

| Level | Name | Per-Tx Limit | Daily Limit | |-------|------|-------------|-------------| | 0 | Suspended | $0 | $0 | | 1 | New | $1 | $10 | | 2 | Basic | $10 | $100 | | 3 | Verified | $100 | $1,000 | | 4 | Trusted | $1,000 | $10,000 | | 5 | Premium | $10,000 | $100,000 |

SDK Methods

| Method | Description | |--------|-------------| | client.pay(request) | Execute a payment (verify + settle); returns a pending result if the server requires human approval | | client.waitForApproval(approvalId, opts?) | Poll a pending approval until settled/denied/expired (or local timeout). Never throws | | client.register(trustLevel?) | Register bot with facilitator | | client.balance() | Get trust status and remaining budget | | client.history(limit?) | Get transaction history | | client.setLimits(limits) | Update spending limits | | client.health() | Check facilitator health |

CLI

The package ships a paybot command — a thin wrapper over PayBotClient. No install needed beyond npx:

npx paybot --help

(Or install globally / as a dependency: npm i -g paybot-sdk then paybot ....)

Configuration

Every command resolves config with precedence flags > environment > error:

| Setting | Flag | Environment variable | |---------|------|----------------------| | API key | --api-key <key> | PAYBOT_API_KEY | | Bot id | --bot-id <id> | PAYBOT_BOT_ID | | Wallet key (for real payments) | (env only) | WALLET_PRIVATE_KEY | | Facilitator URL | --facilitator-url <url> | PAYBOT_FACILITATOR_URL |

Secrets (API keys, wallet keys) are never printed in full — any echoed value is masked as prefix…suffix.

export PAYBOT_API_KEY="pb_test_..."
export PAYBOT_BOT_ID="my-bot"

Commands

# Register this bot (optional initial trust level 0-5)
paybot register --bot-id my-bot --trust-level 2

# Show trust status + remaining budget
paybot balance

# Pay for a resource (amount is human-readable, e.g. 0.05)
paybot pay \
  --resource https://api.example.com/data \
  --amount 0.05 \
  --pay-to 0xRecipient... \
  --token USDC \
  --idempotency-key order-123

# Check facilitator health
paybot health

# List supported networks (CAIP-2 + name)
paybot networks

# List supported tokens (optionally for one network)
paybot tokens
paybot tokens --network eip155:10

| Command | Wraps | |---------|-------| | paybot register [--trust-level <n>] | client.register() | | paybot balance | client.balance() | | paybot pay --resource <url> --amount <human> --pay-to <0x> [--token <SYM>] [--network <caip2>] [--idempotency-key <k>] | client.pay() | | paybot health | client.health() | | paybot networks | getSupportedNetworks() | | paybot tokens [--network <caip2>] | getSupportedTokens() |

Error Handling

Non-pay() methods throw PayBotApiError on failure:

import { PayBotApiError } from 'paybot-sdk';

try {
  await client.balance();
} catch (err) {
  if (err instanceof PayBotApiError) {
    console.log(err.code);       // 'NOT_FOUND'
    console.log(err.statusCode); // 404
    console.log(err.details);    // { botId: 'unknown-bot' }
  }
}

pay() returns PaymentResult with success: false instead of throwing:

const result = await client.pay({ ... });
if (!result.success) {
  console.log(result.error);        // Human-readable message
  console.log(result.errorCode);    // 'TRUST_VIOLATION'
  console.log(result.errorDetails); // { gate: 'SPENDING_ENVELOPE', ... }
}

Network Configuration

import { NETWORKS, getNetwork, getSupportedNetworks } from 'paybot-sdk';

// Available networks
console.log(getSupportedNetworks());
// ['eip155:8453', 'eip155:84532', 'eip155:10', 'eip155:42161', 'eip155:137']

// Get network details
const baseSepolia = getNetwork('eip155:84532');
console.log(baseSepolia?.name); // 'Base Sepolia'

Networks supported

| Network | CAIP-2 | Chain ID | Type | |---|---|---|---| | Base Mainnet | eip155:8453 | 8453 | mainnet | | Base Sepolia | eip155:84532 | 84532 | testnet | | Optimism | eip155:10 | 10 | mainnet | | Arbitrum One | eip155:42161 | 42161 | mainnet | | Polygon PoS | eip155:137 | 137 | mainnet |

RPC URLs default to public endpoints (mainnet.optimism.io, arb1.arbitrum.io/rpc, polygon-rpc.com, …); override per network for production use.

Webhook Signature Verification

Verify inbound webhooks from the facilitator (HMAC-SHA256, constant-time compare, replay-window guard). No extra dependency — uses Node's built-in crypto.

import { verifyWebhookSignature } from 'paybot-sdk';

app.post('/paybot/webhook', (req, res) => {
  const valid = verifyWebhookSignature({
    payload: req.rawBody,                       // raw string/Buffer, exactly as received
    signature: req.headers['paybot-signature'], // 't=<unix_ts>,v1=<hmac_hex>'
    secret: process.env.PAYBOT_WEBHOOK_SECRET,
    tolerance: 300,                             // optional replay window in seconds (default 300)
  });
  if (!valid) return res.status(400).send('invalid signature');
  // ...handle event
  res.sendStatus(200);
});

The signing string is `${t}.${payload}`; the same algorithm is implemented identically in the Python SDK, so a server-signed webhook verifies byte-for-byte in either runtime. signWebhookPayload({ payload, secret }) produces a header value for TS-based senders and tests.

OpenTelemetry (opt-in)

Pass any OpenTelemetry-compatible tracer to emit spans around the payment lifecycle. When no tracer is supplied, telemetry is a zero-overhead no-op — @opentelemetry/api is not a dependency of this SDK.

import { trace } from '@opentelemetry/api';

const client = new PayBotClient({
  apiKey: 'pb_...',
  botId: 'my-bot',
  walletPrivateKey: '0x...',
  telemetry: { tracer: trace.getTracer('my-bot'), prefix: 'paybot.' },
});

Spans emitted per pay(): paybot.client.pay, paybot.x402.sign, paybot.x402.challenge, paybot.x402.settle — with network, amount, bot_id, tx_hash, and success attributes. A returned payment failure is an OK span with success=false; only thrown errors become span ERROR + recordException.

x402 v2 conformance

Tracks the x402-foundation spec: v2 PAYMENT-REQUIRED / PAYMENT-SIGNATURE / PAYMENT-RESPONSE headers (the legacy Payment-Intent path still works), CAIP-2 network identifiers, and the upto (metered/usage) scheme alongside exact.

import { parseCaip2, isSupportedCaip2 } from 'paybot-sdk';

parseCaip2('eip155:8453');        // { namespace: 'eip155', reference: '8453' }
isSupportedCaip2('eip155:8453');  // true

// 'upto' authorizes a capture ceiling; the facilitator settles the actual usage <= max.
// X402Handler.validateUptoCapture(authorizedMax, captured) throws UPTO_OVERCHARGE if exceeded.

Tokens (USDC + EURC + DAI)

Pay in USDC (default), EURC, or DAI. The signing domain is resolved per-token, so each token signs against its own contract. USDC defaults everywhere; the public surface is unchanged for existing USDC callers.

import { getToken, getSupportedTokens } from 'paybot-sdk';

getSupportedTokens();      // ['USDC', 'EURC', 'DAI']
getToken('EURC')?.symbol;  // 'EURC'
getToken('DAI')?.decimals; // 18

await client.pay({
  resource: 'https://api.example.com/data',
  amount: '0.50',
  payTo: '0x....',
  token: 'EURC',           // defaults to 'USDC'; unknown token → UNSUPPORTED_TOKEN
  network: 'eip155:84532', // EURC testnet ships in the public registry
});

Token coverage

Only (token, network) pairs verifiable against an official issuer source are shipped in the public registry. A wrong contract address routes real funds to the wrong contract, so unverifiable pairs are deliberately omitted rather than guessed. The public registry carries only addresses that are safe to ship open-core — mainnet addresses for regulated tokens (e.g. EURC mainnet) are operator-supplied at runtime (see below), not hardcoded here.

| Token | Decimals | Base | Base Sepolia | Optimism | Arbitrum | Polygon | Issuer source | |---|---|---|---|---|---|---|---| | USDC | 6 | ✓ | ✓ | ✓ | ✓ | ✓ | Circle USDC addresses | | EURC | 6 | override | ✓ | — | — | — | Circle EURC addresses | | DAI | 18 | — | — | ✓ | ✓ | ✓ | MakerDAO / Sky |

override = the address is not shipped in the public registry; supply it at runtime via tokenAddressOverrides (see below).

Not currently supported: PYUSD (Paxos — Ethereum + Solana only) and RLUSD (Ripple — Ethereum + XRPL only) are not deployed on any network in this registry, so they are not registered.

Token contract addresses ship with their official issuer source cited inline in src/networks.ts. Re-verify each address against the cited source before mainnet use.

Mainnet token addresses (operator-supplied)

The public registry ships only addresses that are safe to distribute open-core (e.g. testnet deployments). Mainnet addresses for regulated tokens are not hardcoded in the SDK — inject them at runtime via tokenAddressOverrides (symbol → caip2Network → address):

const client = new PayBotClient({
  apiKey: 'pb_...',
  botId: 'my-bot',
  tokenAddressOverrides: {
    EURC: { 'eip155:8453': '0x...' }, // your EURC mainnet contract address
  },
});

Address resolution precedence: explicit PaymentRequest.tokenContracttokenAddressOverrides[symbol][network] → the public registry → otherwise a PaymentResult failure with code TOKEN_ADDRESS_NOT_CONFIGURED. This keeps the SDK from signing against a wrong or absent address when a mainnet token is not configured.

Error Taxonomy

pay() still returns PaymentResult (never throws). The other methods throw a typed hierarchy you can instanceof-switch on — all subclasses remain instanceof PayBotApiError for backward compatibility.

import {
  PayBotError,            // abstract root
  PayBotApiError,         // HTTP-level (unchanged)
  PayBotNetworkError, PayBotTimeoutError, PayBotAuthError,
  PayBotPolicyError,      // trust/AML/daily-limit
  PayBotSignatureError, PayBotSettlementError,
} from 'paybot-sdk';

try {
  await client.balance();
} catch (err) {
  if (err instanceof PayBotPolicyError) { /* trust/limit gate */ }
  else if (err instanceof PayBotAuthError) { /* re-auth */ }
}

Idempotency Keys

Pass an idempotencyKey to pay() / register() — sent as X-Idempotency-Key and deduped in a per-client LRU so a retried call doesn't double-bill.

await client.pay({ resource, amount: '0.01', payTo, idempotencyKey: 'order_42_attempt_1' });
// A second call with the same key returns the cached result with no network round-trip.

Multi-Bot Pool + Spend Treasury

Run many bots in one process from shared operator/transport config, each with its own signing key, under an optional shared daily spend ceiling.

import { PayBotClientPool } from 'paybot-sdk';

const pool = new PayBotClientPool({
  apiKey: 'pb_...',
  operatorId: 'op_1',
  sharedDailyLimitUsd: 500,         // optional treasury across all bots
});
pool.addBot({ botId: 'bot-1', walletPrivateKey: '0x...' });
pool.addBot({ botId: 'bot-2', walletPrivateKey: '0x...' });

// payAs() blocks over-treasury BEFORE any network call (errorCode 'TREASURY_EXCEEDED')
const result = await pool.payAs('bot-1', { resource, amount: '12.00', payTo });
pool.remainingTreasuryUsd();        // 488 after a successful $12 spend

Micropayment Batching

MicropaymentEngine queues many sub-cent payments and settles them as one signed batch, so per-payment gas is amortized across the group. Auto-settle fires when the batch window closes or the count/total thresholds are reached.

import { MicropaymentEngine } from 'paybot-sdk';

const engine = new MicropaymentEngine({
  walletPrivateKey: '0x...',
  batchWindowMs: 60_000,   // default 60s window
  minPaymentCount: 100,    // auto-settle thresholds
  minTotalUsd: 1.0,
});

const id = await engine.queuePayment('0xRecipient...', '0.001');  // returns paymentId
const batch = await engine.batchPayments([id]);                   // signed BatchedSettlement
engine.getQueueStatistics();                                      // BatchStatistics

AP2 + MPP

paybot is a clean x402 settlement engine, so a Google AP2 (A2A x402-extension) mandate can settle through it directly. MPP (Stripe/Tempo) is still preview — the SDK ships only a detect-and-route capability seam, not a full client.

import { Ap2Adapter, detectMppCapability } from 'paybot-sdk';

const ap2 = new Ap2Adapter(handler);              // handler: X402Handler
if (ap2.validateMandate(mandate).valid) {
  const receipt = await ap2.settle(mandate);      // signs + submits via x402
}

detectMppCapability(responseHeaders);             // { supported, mode: 'detect-only'|'none', specVersion? }
// createMppSeam().settle(...) throws MPP_NOT_IMPLEMENTED (501) — full MPP deferred until GA

The AP2 adapter settles the payment; it does not verify the AP2 verifiable-credential signature — that stays in the mandate issuer's trust domain.

Python SDK

A Python port lives in packages/python (paybot-sdk on PyPI, >=3.10). Mirrors the TS client surface, real EIP-3009 signing via eth-account, and the identical webhook verification contract.

from paybot_sdk import PayBotClient, PayBotConfig, PaymentRequest, verify_webhook_signature

client = PayBotClient(PayBotConfig(api_key="pb_...", bot_id="my-bot", wallet_private_key="0x..."))
result = await client.pay(PaymentRequest(resource="https://api.example.com/data",
                                         amount="0.01", pay_to="0x...."))

MCP Integration

For AI agent frameworks, use paybot-mcp which wraps this SDK as an MCP server.

Roadmap & Status

Legend: ✅ shipped · 🟡 partial · 🔭 deferred (intentional) · ⬜ gap

Capability map — what the SDK can do today:

paybot-sdk capability map

Roadmap ahead — the phased plan:

paybot-sdk roadmap

What we've built

Core rail (hardened): PayBotClient (pay/balance/history/setLimits/register/health/commission/API-keys) · x402 auto-handler · EIP-3009 signing · MicropaymentEngine · trust levels 0–5 · commission accounting · paybot402() middleware · self-hostable facilitator · CI hardening (CodeQL, OSV, 80% coverage gate, SHA-pinned actions).

| Shipped | Gap ID | |---------|--------| | ✅ x402 v2 conformanceupto scheme, PAYMENT-* headers, CAIP-2 helpers | T1.1 + new | | ✅ Webhook signature verification (TS + Python, byte-identical HMAC) | T1.2 | | ✅ Idempotency keys (X-Idempotency-Key + LRU dedupe) | T1.3 | | ✅ Error taxonomy (PayBotError + 6 typed subclasses) | T1.4 | | ✅ OpenTelemetry hooks (opt-in, zero new deps) | T1.5 | | ✅ Multi-bot pool + spend treasury | T1.6 | | ✅ EURC + token registry (per-token EIP-712 domains) | T2.2 | | ✅ AP2 settlement adapter · 🔭 thin MPP capability seam (deferred to GA) | T2.3 | | ✅ Python SDK 0.1.0 (real EIP-3009 signing) | T3.2 (partial) |

Tier 1 (credibility blockers) is 100% complete (T1.1–T1.6). Test posture: 331 TS tests / 98.66% coverage · 52 Python tests.

Current gaps

  • Network expansion (T2.1) — still Base + Base Sepolia only; add Optimism / Arbitrum / Polygon.
  • CCTP V2 cross-chain receive — ⏰ time-sensitive: Circle CCTP V1 deprecates 2026-07-31.
  • Refund + reversal helpers (T2.4) · ⬜ Streaming subscriptions (T2.5) · ⬜ Wallet-connect bridge (T2.6)
  • 🟡 Token breadth — USDC + EURC done; PYUSD / RLUSD / DAI not added (operator-gated).
  • 🟡 Language ports (T3.2) — Python runtime shipped (middleware/x402-handler unported); Go / Rust not started.
  • Framework ports (T3.1) (Hono/Next/NestJS/FastAPI/Django) · ⬜ CLI (T3.4) · ⬜ Examples + tutorial site (T3.5) · ⬜ More MCP tools (T3.3) · ⬜ L402 shim (T3.6)
  • 🔭 Full MPP (T2.3) — deliberately deferred; Stripe/Tempo MPP is still preview and shares no signing code with EIP-3009. Detect-only seam ships now.

Roadmap ahead

Prioritized by internal gap severity × external leverage (EU-bank credibility, live distribution channels, hard deadlines).

Phase A — Near-term (rail credibility):

  1. Network expansion (Optimism + Arbitrum + Polygon) — closes the biggest surface gap vs. Coinbase/Circle/Crossmint.
  2. CCTP V2 cross-chain receive — ⏰ hard deadline (CCTP V1 dies 2026-07-31).
  3. Refund + reversal helpers — table-stakes for real commerce.
  4. Token breadth (PYUSD/RLUSD/DAI, operator-gated).

Phase B — Mid-term (agent-economy surface): streaming subscriptions · CLI · framework ports (Hono/Next/FastAPI first) · examples + tutorial site · wallet-connect bridge.

Phase C — Strategic / opportunistic: full MPP (on GA) · more language ports (Go/Rust) · L402/Lightning shim · more MCP tools.

Strategic posture: the moat is self-hosted, non-custodial, MIT, trust-layer-in-the-SDK — which custodial/portal-locked rivals (Coinbase Agentic Wallets, Circle, Crossmint, Payman) structurally cannot copy. Being a clean x402 settlement engine makes paybot AP2-pluggable and AgentCore-compatible today — so MPP can wait for GA.

Deployment Options

Option 1: Hosted (Recommended)

Use the hosted facilitator at api.paybotcore.com — no setup needed, ready to go:

const client = new PayBotClient({
  apiKey: 'pb_test_...',
  botId: 'my-bot',
  facilitatorUrl: 'https://api.paybotcore.com',  // ← Hosted
});

Option 2: Self-Hosted with Docker

For enterprise bots or custom networks, deploy your own PayBot facilitator with Docker (5 minutes):

git clone https://github.com/RBKunnela/paybot-core.git
cd paybot-core
docker compose up -d

Then configure your bot:

const client = new PayBotClient({
  apiKey: 'pb_dev_...',
  botId: 'my-bot',
  facilitatorUrl: 'http://localhost:3000',  // ← Self-hosted
});

Quick start guide: See SELF_HOSTING.md in this repository.

Full deployment guide: See DEPLOYMENT.md in paybot-core repository.

Contributing

We take security seriously. All PRs are reviewed by an army of AI agents from different specializations (@dev, @qa, @architect, @security, @devops) before acceptance. This ensures code quality, correctness, security validation, and architectural alignment.

See CONTRIBUTING.md for details.

License

MIT