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

@ucash/agents

v0.6.11

Published

Monetize AI agents with non-custodial HTTP-402 crypto payments. Zero-dependency JS client for agents.u.cash: sell priced resources, get paid to your own wallet across 40+ chains, with scoped subagent keys.

Readme

@ucash/agents

npm version npm downloads license GitHub stars

Zero-dependency JavaScript client for the agents.u.cash API - the 402 Online Protocol for non-custodial agent payments. Works for both sides of the network: an agent selling (manage resources, watch settlements) and a buyer (fetch a 402 door, pay, and settle - automatically via on-chain detection, or instantly by submitting the tx hash). Supports ~40 native coins (Bitcoin + Lightning, Ethereum + 15 EVM L2s, Solana, Tron, XRP, USDT/USDC, USDC-on-Base gasless, UCASH) plus custom tokens on 20 chains (ERC-20 / TRC-20 / SPL / ...) - see agents.u.cash for the full live list.

Uses the global fetch (Node 18+ and modern browsers). No dependencies.

Highlights

  • Monetize AI agents pay-per-call: sell priced resources over HTTP 402; buyers pay direct to your own wallet.
  • Non-custodial: the platform never holds funds. 40+ chains (BTC + Lightning, ETH + 15 EVM L2s, SOL, TRX, XRP, USDC-on-Base, UCASH, custom tokens).
  • Zero-dependency JS client (Node 18+ / browsers) + a Python SDK + an MCP server (77 tools).
  • Scoped subagent (RBAC) keys, per-caller spend caps, HMAC-signed settlement webhooks with a built-in verifier.
  • Two-sided: sell (resources, one-off payment links, shop, stores, landers, payout-info/OTC) and buy (402 door, UCP checkout sessions).

Docs: agents.u.cash · OpenAPI · llms.txt · CHANGELOG

Contents

Install

npm install @ucash/agents

Or copy index.js - it has no dependencies.

Sell (as an agent)

import { AgentsUCash } from '@ucash/agents';

// Get an API key once (wallet-first; works at $0 immediately - verify email optionally for free credit):
// const { api_key } = await new AgentsUCash().signup({ email: '[email protected]', password: 'longpass' });

const agent = new AgentsUCash({ apiKey: process.env.UXC_API_KEY });
await agent.setWallet('btc', 'bc1q…');
await agent.setWebhook('https://my.bot/webhook');

const { res_id, checkout_url } = await agent.createResource({ amount: 0.05 }); // priced resource
const { accepts } = await agent.createChallenge(res_id);                // what a buyer pays
// Share the checkout_url - the pay.u.cash buyer door (/checkout/<enc>?cloud=<token>):
// a human opens the HTML checkout, an agent fetches checkout_url + '?agent=1' for the 402 manifest,
// an x402 client sends X-PAYMENT. (https://agents.u.cash/r/{res_id} is a back-compat alias.)
// Exact vs dust: save 3+ addresses per coin (await agent.setWallet('ucash', '0xaaa...,0xbbb...,0xccc...'))
// for exact-amount unique-address payments (500.00000000); one address adds sub-unit dust (500.00004180).

// One-off link instead of a persistent resource (closes when paid or after expiry):
const { url } = await agent.createPaymentRequest({ amount: 5, expiry: '24h' }); // -> pay.u.cash /id/<enc>

const settled = await agent.getSettlements();                          // your earnings log

Buy (as a buyer)

import { AgentsUCash } from '@ucash/agents';

const buyer = new AgentsUCash();                       // no key needed to buy
const { accepts } = await buyer.viewDoor(resId);       // the 402 door (JSON)
// 1. pick an entry, pay entry.payTo exactly entry.amount from your wallet (out of band)
// 2. the platform auto-detects the on-chain payment and settles it.
//    Optionally POST the tx hash to settle instantly instead:
const result = await buyer.verify(accepts[0].challengeId, txHash);
// -> { settled: true } | { status: 'pending', confirmations, required }

A human-friendly payable page is also available: await buyer.viewDoor(resId, { html: true }) returns the HTML.

UCP checkout sessions (buyer)

Multi-item, mixed-currency carts over the Universal Commerce Protocol. The merchant is resolved from the custom-domain baseUrl, or from a cloud merchant token on the shared host. No API key.

const buyer = new AgentsUCash();   // baseUrl = the merchant's domain (or the platform host + cloud)
const cart = await buyer.createCheckout({
  lineItems: [{ item: { id: resIdA }, quantity: 1 }, { item: { id: resIdB }, quantity: 2 }],
  currency: 'USD',   // optional: cart currency for mixed-currency carts
  cloud: '<merchant-token>',   // only on the shared platform host
});
// -> { id, status:'incomplete', currency, line_items, totals, ap2:{ merchant_authorization, nonce } }
const ready = await buyer.completeCheckout(cart.id, { cloud: '<merchant-token>' });
// -> ready_for_complete + payment_handlers[] (pay each challenge on-chain)
const order = await buyer.getOrder(cart.id, { cloud: '<merchant-token>' });   // per-item fulfillment status

Optional AP2 (dev.ucp.shopping.ap2_mandate): pass completeCheckout(id, { ap2: { checkout_mandate }, cloud }) with a buyer-signed SD-JWT-VC for holder-proof authorization. Responses are RFC 9421-signed (ES256) with the merchant key.

Manage your store (full merchant surface)

Beyond priced 402 resources, an agent is a first-class merchant over its own account: full transaction history + actions, multi-store, shop products, landers, payout-info/OTC, discount codes, checkout custom fields, and billing. All key-authenticated; tenant-scoped to the agent.

// Transactions: full history, CSV export, + actions
const txs = await agent.getTransactions({ status: 'C', limit: 50 });
const csv = await agent.downloadTransactions({ dateFrom: '2026-01-01' });
await agent.refundTransaction(txId);          // self-guarding: only if you connected a refund-capable node/coinbase
await agent.resendWebhook(txId);
await agent.submitHash(txId, '0xabc...');

// Stores (sub-merchants)
const store = await agent.createStore({ label: 'Store B' });   // api_key+cloud_token+webhook_secret returned once
await agent.rotateStoreCredential(store.store.id, 'cloud_token');

// Shop products, landers, payout-info/OTC
await agent.createShopProduct({ title: 'Ebook', price: 9.99, currency: 'USD' });
await agent.createLander({ checkoutId: flagId });
await agent.createPayoutInfo({ amount: 50, currency: 'USD', email: '[email protected]' }); // emails the payee a link

// Discount codes (amount = price multiplier: 0.9 = 10% off) + checkout custom fields
await agent.addDiscountCode({ code: 'LAUNCH', amount: 0.9, checkoutIds: 'all' });
await agent.addCustomField({ type: 'select', label: 'Size', options: ['S', 'M', 'L'] });

// Billing: balances + capacity
const bill = await agent.getBilling();         // { credit_balance, ucash_points, lander_slots:{...} }
await agent.buyLanderPack(10);                 // debits credit_balance, grows slots
await agent.redeemUcash(1000);                 // fee-credit points -> fee credit

Subagents (scoped RBAC keys)

Delegate a LIMITED credential to another automated principal. A subagent is a scoped sa_ API key with a staff role: it authenticates against your tenant, but the existing RBAC (uxc_can) enforces a limited capability set on every endpoint, so it can only do what the role allows. Owner-only (you create them; a subagent cannot create subagents). Reuses the merchant staff model + seat billing (a subagent with no free slot debits credit_balance).

// Built-in role: a clerk can create resources + read, but not edit settings, refund, or manage stores
const sub = await agent.createSubagent({ role: 'clerk', displayName: 'Fulfillment bot' });
// sub.api_key is the sa_ key, returned ONCE. The key works on every /v1/* endpoint; writes its role
// lacks return 403, and managing stores stays owner-only.

// Or a custom permission set (slugs: transactions.view, checkouts.edit, payment-links.create, ...).
// storeScope (store ids) restricts it to those stores (empty = all stores).
const ro = await agent.createSubagent({
  role: 'custom', permissions: ['transactions.view', 'checkouts.view'], storeScope: [storeId],
});

await agent.getSubagents();                                     // list (never returns the api_key)
await agent.updateSubagent(sub.subagent.id, { role: 'manager', status: 'suspended' });
const fresh = await agent.rotateSubagentKey(sub.subagent.id);   // old key stops working; new key ONCE
await agent.deleteSubagent(sub.subagent.id);                    // key stops working; seat slot frees

Spend caps (per-caller limits)

Cap how much a single payer can spend on a resource in a rolling window. A payer whose settled spend in the window reaches amount is refused new authorization (x402 before-charge; on detect the platform refuses to credit an over-cap payment). Caller identity is the payer wallet (or, with by: 'ip', the buyer IP, enforced at the door before any charge). Optional; off by default.

const res = await agent.createResource({ amount: 0.05, currency: 'USD', maxPerCaller: { amount: 1.00, windowHours: 24 } }); // add by:'ip' to cap by buyer IP at the door instead of by wallet
await agent.setResourceCap(res.res_id, { amount: 0.50, windowHours: 6 });   // change it
await agent.setResourceCap(res.res_id, null);                               // clear it
const callers = await agent.getResourceCallers(res.res_id);                 // [{ caller, spend, payments }]
await agent.deleteResource(res.res_id);                                    // soft-disable (settled history kept)
const relay = await agent.getExactRelay();                                 // gasless relayer config (on by default)
await agent.setExactRelay(false);                                          // opt out to buyer-pays-gas (gas + 21% while on)

Verifying webhooks

When a payment settles, agents.u.cash POSTs an HMAC-signed event to your webhook URL. Verify it with the static helper (it runs on your server; no instance or API key needed). The signature is HMAC-SHA256 of "<t>.<rawBody>" in the X-Webhook-Signature: t=<unix>,v1=<hex> header. Use the raw request body - re-encoding the JSON breaks the signature.

import { AgentsUCash } from '@ucash/agents';

// Express - capture the RAW body on this route (do NOT put express.json() in front of it):
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const valid = await AgentsUCash.verifyWebhookSignature(
    req.body.toString(),                    // the EXACT raw bytes you received
    req.get('X-Webhook-Signature'),         // t=<unix>,v1=<hex>
    process.env.UXC_WEBHOOK_SECRET,         // from setWebhook()/rotateWebhookSecret() - shown ONCE
  );
  if (!valid) return res.status(401).send('bad signature');
  const event = JSON.parse(req.body.toString());
  // Deduplicate by event.event_id (also in X-Webhook-Event-Id) - a settled txn may be delivered >1x.
  res.status(200).send('ok');
});

The 300-second replay window is on by default; pass { tolerance: 0 } to skip the freshness check.

Asset codes

acceptedAssets takes coin codes (omit it to accept all your configured wallets). Common built-in codes:

| Coin | Code | Note | |---|---|---| | Bitcoin | btc | | | Bitcoin Lightning | btc_ln | NOT lightning or btc-ln | | Ethereum | eth | | | EVM L2s | eth_base, eth_arb, eth_op, eth_linea, eth_unichain, eth_world, eth_scroll, eth_ink, eth_abstract, eth_plasma | plus native mnt, bera, s, mon, hype | | USDC on Base | usdc_base | the x402 gasless rail | | Stablecoins | usdc, usdt, usdt_tron, usdt_bsc | | | Others | sol, trx, xrp, ltc, doge, bnb, pol, avax, xmr, algo, bch, dot, xlm, xtz, ucash | |

Plus any custom-token code you added via setCustomToken(). A misspelled code throws with .code = 'uxc_unknown_asset' ("Unsupported asset: X"). The live canonical list grows as chains are added - see agents.u.cash.

Resource vs payment request

Two ways to get paid - pick by use case:

| | createResource | createPaymentRequest | |---|---|---| | Lifetime | persistent (payable many times) | one-off link, closes on pay or expiry | | Door | /r/{res_id} / checkout_url | /id/<enc> url | | Multi-coin acceptedAssets | yes | no | | Per-caller cap (maxPerCaller) | yes | no | | title / note / expiry / redirect / externalReference | no | yes |

A resource is a standing price buyers pay repeatedly; a payment request is a single invoice link.

API

| Method | Auth | Description | |---|---|---| | signup({ email, password, primaryWallet? }) | - | Register; returns api_key | | topUp(amount) | key | Create a ≥$1 top-up checkout (adds platform credit; activates if not yet) | | getAgent() | key | Account snapshot (balance, wallets, webhook, earnings summary) | | setWebhook(url) | key | Set the settlement webhook (auto-generates the HMAC secret; shown once) | | getWebhook() / rotateWebhookSecret() / clearWebhook() | key | Read (masked) / rotate / clear the webhook | | AgentsUCash.verifyWebhookSignature(rawBody, sigHeader, secret, { tolerance? }) | - | Verify an incoming webhook's HMAC signature (static; runs on your server) | | setWallet(asset, address) | key | Set your receive address for an asset | | setStripe({ secretKey, productId, webhookSecret, publishableKey? }) | key | Connect your Stripe account (card rail); verifies the key + product | | getStripe() | key | Masked Stripe config + the webhook endpoint to register | | clearStripe() | key | Disconnect your Stripe account | | setCustomToken({ type, code, contractAddress, decimals, name, rate?, rateUrl? }) | key | Add a custom token (ERC-20/TRC-20/SPL); then setWallet({ asset: code, address }) to set its receive address | | getCustomTokens() | key | List your custom tokens | | deleteCustomToken(code) | key | Remove a custom token | | getSettings() | key | Read safe settings (confirmations, webhook url+secret, currency, payment prefs, notifications, branding) | | setSettings(partial) | key | Partially update safe settings | | getIntegrations() | key | Read stored third-party integration credentials (Discord, Telegram, BigCommerce, Ecwid, Wix) | | setIntegrations(integrations) | key | Store third-party integration credentials | | createResource({ amount, currency?, acceptedAssets?, webhookUrl? }) | key | Create a priced resource | | getResources(resId?) | key | List resources, or fetch one | | createChallenge(resId) | key | Build the multi-coin accepts[] | | verify(challengeId, hash) | key optional | Verify + settle (buyer-push: no key needed) | | getSettlements() | key | Earnings log | | viewDoor(resId, { html? }) | - | The public 402 door (JSON, or HTML) | | createCheckout({ lineItems, currency?, buyer?, context?, cloud? }) | - | UCP checkout session (multi-item, mixed-currency cart) | | getCheckout(id, { cloud? }) | - | Fetch a checkout session | | completeCheckout(id, { ap2?, cloud? }) | - | Mint challenges → ready_for_complete (optional AP2 mandate) | | cancelCheckout(id, { cloud? }) | - | Cancel a checkout session | | getOrder(id, { cloud? }) | - | A checkout session as a UCP order (per-item fulfillment) | | searchCatalog({ query?, filters?, pagination?, cloud? }) | - | Search the merchant catalog | | getProduct(id, { cloud? }) | - | Fetch a single catalog product by id | | lookupProducts(ids, { cloud? }) | - | Batch catalog lookup by ids | | getTransactions({ status?, search?, cryptocurrency?, checkoutId?, dateFrom?, dateTo?, limit?, offset? }) | key | Full transaction history | | getTransaction(id, { webhookLog? }) | key | One transaction, or its webhook log | | downloadTransactions({ ...filters }) | key | CSV export (raw text) | | refundTransaction(id) | key | Refund (guarded: needs a connected refund-capable node/coinbase) | | resendWebhook(id) | key | Force a webhook re-delivery | | submitHash(id, hash) | key | Attach an on-chain hash (replay-guarded) | | getStores() / createStore({ label?, webhookUrl? }) / updateStore(id, { label?, webhookUrl? }) / deleteStore(id) | key | Multi-store CRUD | | rotateStoreCredential(id, which) / testStoreWebhook(id) | key | Rotate api_key/webhook_secret/cloud_token; send a test webhook | | getShopProducts(id?) / createShopProduct(fields) / updateShopProduct(id, fields) / deleteShopProduct(id) | key | Shop products (/v1/checkouts) | | getLanders({ landerId?, offers? }) / createLander({ checkoutId, tpl? }) / updateLander({ landerId?, offerId?, ... }) / deleteLander(id) | key | Landers + offer status | | createPayoutInfo({ amount, currency, ... }) / getPayoutInfo(id) / completePayout(id) | key | Payout-info / OTC requests | | getDiscountCodes() / addDiscountCode({ code, amount, checkoutIds? }) / setDiscountCodes([...]) / deleteDiscountCode(code) | key | Discount codes (amount = multiplier) | | getCustomFields() / addCustomField(field) / setCustomFields({ customFields, title? }) / deleteCustomField(index) | key | Checkout custom fields | | getBilling() / buyLanderPack(qty) / redeemUcash(amount) | key | Balances + capacity (lander pack, fee-credit redeem) |

All calls return the parsed response object and throw on API errors (the error has .code and .status).

Input contracts (strict)

The API validates every write before touching state, so nothing is ever silently ignored:

  • Unknown or misspelled keys -> 400 uxc_unknown_param listing the Allowed keys (this SDK's assertKeys raises the same error client-side before the request is sent).
  • Wrong shapes -> 400 uxc_invalid_request naming the field + expected shape, e.g. confirmations must be an object: {default, coins, increase}.
  • Booleans must be true/false (or the strings "true"/"false"/"1"/"0"); "yes"/"maybe" are 400s.
  • Updates are true partials: unsent fields keep their values (updateShopProduct(id, {price: 5}) changes only the price).
  • Re-setting an existing webhook secret returns has_secret: true + a hint; use rotateWebhookSecret() to mint + reveal a new one.

Error handling

All calls return the parsed response object on success and throw on errors; the thrown error carries .code and .status.

try {
  await agent.createResource({ amount: 0.05 });
} catch (e) {
  console.error(e.code, e.status, e.message);   // e.g. 'uxc_agent_not_activated' 402
}

Status codes you will see:

| Status | Meaning | SDK behavior | |---|---|---| | 200 | success | returns response | | 202 | pending (x402 verified, awaiting on-chain settlement) | returns response with status: 'pending' (does NOT throw) | | 402 | payment required / agent not activated | throws, .status = 402 | | 410 | challenge expired | throws, .code = 'uxc_challenge_expired' | | 429 | rate limited | throws, .status = 429 |

verify(challengeId, hash) returns { settled: true } on success, { status: 'pending', confirmations, required } while the payment awaits on-chain confirmation, or { status: 'underpaid' }. An expired challenge throws with .code === 'uxc_challenge_expired' (HTTP 410 Gone) - catch on .code:

try {
  const r = await buyer.verify(challengeId, txHash);
} catch (e) {
  if (e.code === 'uxc_challenge_expired') { /* re-fetch accepts[] and retry */ }
  else throw e;
}

Non-custodial: the platform never holds funds - every payTo is the seller's own wallet, and this client never sees your wallet keys.