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

@dexterai/x402

v3.9.0

Published

Full-stack x402 SDK - add paid API monetization to any endpoint. Express middleware, React hooks, Access Pass, dynamic pricing. Solana, Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE.

Readme


What is x402?

x402 is HTTP's missing payment protocol. A server returns 402 Payment Required with a PAYMENT-REQUIRED header describing what it wants paid; the client signs a payment, retries with PAYMENT-SIGNATURE, and gets the resource.

The audience this is built for in 2026 is agents: Claude, ChatGPT, Cursor, and the rest, making paid HTTP calls on behalf of humans. This SDK is the buyer side and the seller side, with USDC on Solana and the major EVM chains, behind a single API.

You call payAndFetch() on the client. You add x402Middleware() on the server. Payments happen.


Quick start

npm install @dexterai/x402

Pay for a resource (Node.js, any chain)

import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';

const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY);  // requires: npm install viem

const result = await payAndFetch(
  'https://api.example.com/protected',
  { method: 'GET' },
  { solana, evm },
  {},
);

if (result.ok && result.paid) {
  const data = await result.response.json();
  console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
} else if (result.ok && !result.paid) {
  // Endpoint didn't demand payment; response came through unchanged.
  const data = await result.response.json();
} else {
  console.error(result.reason, result.detail);
}

payAndFetch is version-agnostic (handles x402 v1 and v2 transparently) and returns a discriminated PayResult. The ok: true branch is further split by paid: true | false, so a free 200 response is distinguishable from an actually-paid one. No throws for expected failures.

Pay for a resource (Browser, React)

useX402Payment accepts wallets from your existing providers (@solana/wallet-adapter-react, wagmi) and exposes a fetch that pays automatically.

import { useX402Payment } from '@dexterai/x402/react';
import { useWallet } from '@solana/wallet-adapter-react';
import { useAccount } from 'wagmi';

function PayButton({ url }: { url: string }) {
  const solanaWallet = useWallet();
  const evmWallet = useAccount();

  const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
    wallets: { solana: solanaWallet, evm: evmWallet },
  });

  return (
    <div>
      <p>Balance: ${balances[0]?.balance.toFixed(2)}</p>
      <button onClick={() => fetch(url)} disabled={isLoading}>
        {isLoading ? 'Paying…' : 'Pay'}
      </button>
      {transactionUrl && <a href={transactionUrl}>View transaction</a>}
    </div>
  );
}

Protect an endpoint (server)

import express from 'express';
import { x402Middleware } from '@dexterai/x402/server';

const app = express();

app.get(
  '/api/protected',
  x402Middleware({
    payTo: 'YourReceivingAddress',
    amount: '0.01',                // $0.01 USDC
    network: 'eip155:8453',        // Base. Pass an array for multi-chain.
  }),
  (req, res) => res.json({ data: 'protected content' }),
);

The handler only runs after a successful payment. Pass network as an array to accept across multiple chains; the buyer picks the chain they have balance on.

Reading the receipt

getPaymentReceipt(response) returns the settled-payment info attached to any paid response (whether the payment came from payAndFetch, the legacy wrapFetch, or the React hook).

import { payAndFetch, getPaymentReceipt } from '@dexterai/x402/client';

const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
if (result.ok && result.paid) {
  const receipt = getPaymentReceipt(result.response);
  console.log('tx:', receipt?.transaction, 'on', receipt?.network);
}

Batch settlement (EVM)

Batch settlement lets a buyer pre-fund an escrow channel once, make many discrete paid API calls against it with cheap off-chain vouchers, and then close the channel. The seller's many charges are batched into a handful of on-chain transactions instead of one per call. It amortizes gas across high-frequency discrete purchasing.

It is not a streaming primitive; it batches discrete purchases. EVM only (Base, Arbitrum, Polygon). The buyer never needs a gas token: every step (deposit, voucher, claim, settle, refund) is signature-based; the Dexter facilitator submits the transactions and pays the gas.

Buyer

import { openBatchChannel } from '@dexterai/x402/batch-settlement';

const channel = await openBatchChannel({
  wallet: evmWallet,            // any { address, signTypedData }
  network: 'eip155:8453',       // Base
  deposit: '0.30',              // USDC escrowed for this channel
});

const a = await channel.fetch('https://api.example.com/v1/data');
const b = await channel.fetch('https://api.example.com/v1/data');

console.log(channel.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }

const { closed } = await channel.close();

Each openBatchChannel call opens a new channel: a fresh random channel-config salt is generated, so a buyer can hold several independent channels with the same seller over time. The salt is exposed as channel.salt; persist it if you will later need to resume that exact channel.

Resume after a process restart with the wallet, network, and the channel's salt:

import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';

const channel = await resumeBatchChannel({
  wallet: evmWallet,
  network: 'eip155:8453',
  salt: savedSalt,
});

Channel state auto-persists (localStorage in the browser, a file under ~/.dexter-x402/channels in Node); the resumed channel's accounting is recovered from storage, or from on-chain state if storage was lost.

Escape hatch: forceWithdraw() / finalizeWithdraw()

If the seller never settles, the buyer can reclaim unspent escrow directly via the channel contract's timed withdrawal:

await channel.forceWithdraw();
// after the channel's withdraw delay elapses
await channel.finalizeWithdraw();

Last-resort safety net; normal operation never needs it. Unlike every other batch-settlement step, the escape hatch costs the buyer gas: the wallet must expose a sendTransaction method.

Seller

createBatchSettlementSeller(config) returns an Express request handler. Mount it directly; it accepts vouchers, persists them, and settles in the background. Dexter operates the delegate authorizer, so the seller manages no signing key.

import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';

const seller = createBatchSettlementSeller({
  payTo: '0xYourReceivingAddress',
  network: 'eip155:8453',
  price: '0.08',
});

app.use('/api/data', seller);

process.on('SIGTERM', async () => {
  await seller.stop();   // flushes a final settle so no vouchers are lost
});

Mounting via x402Middleware also works. With scheme: 'batch-settlement' it returns the same callable seller object, so you keep the .stop() / .closeAll() / .closeChannel() handle.


Discovery (bazaar extension)

Shipped in 3.8.0. The bazaar extension makes any x402Middleware-protected route discoverable through the official x402 bazaar spec, so agents browsing a bazaar-compliant indexer find your endpoint by capability, not by URL.

The 402 response carries a spec-compliant extensions.bazaar block describing the route's inputs, output schema, and template path. Discovery indexers read it and surface your endpoint in agent-facing catalogs.

import {
  x402Middleware,
  bazaarExtension,
  declareDiscoveryExtension,
} from '@dexterai/x402/server';

app.post(
  '/v1/translate',
  x402Middleware({
    payTo: '...',
    amount: '0.02',
    network: 'eip155:8453',
    extensions: [bazaarExtension()],
    declarations: {
      ...declareDiscoveryExtension({
        method: 'POST',
        bodyType: 'json',
        inputSchema: {
          properties: {
            text: { type: 'string', description: 'Source text' },
            targetLang: { type: 'string', description: 'ISO 639-1 code' },
          },
          required: ['text', 'targetLang'],
        },
        output: {
          example: { translation: 'Bonjour' },
        },
      }),
    },
  }),
  (req, res) => res.json({ translation: translate(req.body) }),
);

extensions is opt-in: middleware without an extensions array emits a 402 byte-identical to pre-3.8.0 behavior. method may be omitted from declareDiscoveryExtension; the extension stamps the actual request method at 402 time.

Failure isolation: if an extension throws, it's caught, logged, and skipped. The 402 still goes out, just without that key. The payment path is never affected.


Sponsored Access (Instinct ad network)

This is how MCP agents (Claude, ChatGPT, Cursor) see your sponsored placements. When an agent pays for an API through Dexter's facilitator, a matched recommendation can be injected into the settlement receipt; the agent's LLM reads it and may call the suggested resource next. Both blockchain transactions become proof of the conversion.

The buyer-side helpers are wired into every MCP fetch tool in the Dexter ecosystem, plus the human-facing receipt UI on x402gle. If you're shipping an x402 endpoint, sponsored access is how you reach the agents already using paid APIs.

Seller: enable recommendation injection

import { x402Middleware } from '@dexterai/x402/server';

app.get(
  '/api/data',
  x402Middleware({
    payTo: '...',
    amount: '0.01',
    sponsoredAccess: true,         // injects _x402_sponsored into JSON responses
  }),
  (req, res) => res.json({ data: 'content' }),
);
// Response: { _x402_sponsored: [{ resourceUrl, description, sponsor }], data: 'content' }

For custom placement (where in the body the recommendation appears, conversion logging, etc.), pass an object instead of true:

sponsoredAccess: {
  inject: (body, recs) => ({ ...body, related_tools: recs }),
  onMatch: (recs, settlement) => log(`matched ${recs.length} for tx ${settlement.transaction}`),
},

Buyer: read recommendations off a paid response

import {
  payAndFetch,
  getSponsoredRecommendations,
  fireImpressionBeacon,
} from '@dexterai/x402/client';

const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
if (result.ok && result.paid) {
  const recs = getSponsoredRecommendations(result.response);
  if (recs) {
    for (const rec of recs) {
      console.log(`${rec.sponsor}: ${rec.description} (${rec.resourceUrl})`);
    }
    await fireImpressionBeacon(result.response);
  }
}

React: recommendations in the hook

import { useX402Payment } from '@dexterai/x402/react';

function PayButton() {
  const { fetch, isLoading, sponsoredRecommendations } = useX402Payment({ wallets });

  return (
    <div>
      <button onClick={() => fetch(url)} disabled={isLoading}>Pay</button>
      {sponsoredRecommendations?.map((rec, i) => (
        <a key={i} href={rec.resourceUrl}>{rec.sponsor}: {rec.description}</a>
      ))}
    </div>
  );
}

Advertise

Campaign creation is x402-gated at x402ads.io. Your wallet is your identity. Full advertiser guide at docs.dexter.cash/docs/sponsored-access/for-advertisers.


Auto-listing in OpenDexter

When an agent pays for your API through the Dexter facilitator, your endpoint is auto-discovered, AI-named, and quality-tested. Quality-verified endpoints surface in x402_search results across MCP clients (ChatGPT, Claude, Cursor). No registration step.

Browse the live catalog at dexter.cash/opendexter.


Supported networks

All networks supported by the Dexter facilitator. USDC on every chain.

Mainnets:

| Network | CAIP-2 | Status | |---------|--------|--------| | Solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | Production | | Base | eip155:8453 | Production | | Polygon | eip155:137 | Production | | Arbitrum | eip155:42161 | Production | | Optimism | eip155:10 | Production | | Avalanche | eip155:43114 | Production | | BSC | eip155:56 | Production | | SKALE Base | eip155:1187947933 | Production (zero gas) |

Testnets:

| Network | CAIP-2 | |---------|--------| | Solana Devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 | | Solana Testnet | solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z | | Base Sepolia | eip155:84532 | | SKALE Base Sepolia | eip155:324705682 |

Multi-chain endpoints accept payments on any chain in the list. The buyer picks:

app.get('/api/data', x402Middleware({
  payTo: {
    'solana:*': 'YourSolanaAddress...',
    'eip155:*': '0xYourEvmAddress...',
  },
  amount: '0.01',
  network: [
    'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
    'eip155:8453',
    'eip155:137',
    'eip155:42161',
    'eip155:10',
    'eip155:43114',
    'eip155:56',
    'eip155:1187947933',
  ],
}));

Package exports

// Client: canonical entrypoint
import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';

// Client: sponsored access reader
import { getSponsoredRecommendations, fireImpressionBeacon } from '@dexterai/x402/client';

// React
import { useX402Payment } from '@dexterai/x402/react';

// Server: middleware
import { x402Middleware } from '@dexterai/x402/server';

// Server: discovery (bazaar extension)
import { bazaarExtension, declareDiscoveryExtension } from '@dexterai/x402/server';

// Server: manual control
import { createX402Server } from '@dexterai/x402/server';

// Batch settlement
import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';

// Adapters (advanced)
import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';

// Utilities
import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';

Utilities

import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';

toAtomicUnits(0.05, 6);          // '50000'
toAtomicUnits(1.50, 6);          // '1500000'
fromAtomicUnits('50000', 6);     // 0.05
fromAtomicUnits(1500000n, 6);    // 1.5

Manual server (advanced)

For full control over the payment flow without x402Middleware:

import { createX402Server } from '@dexterai/x402/server';

const server = createX402Server({
  payTo: 'YourAddress...',
  network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
});

app.post('/protected', async (req, res) => {
  const paymentSig = req.headers['payment-signature'];

  if (!paymentSig) {
    const requirements = await server.buildRequirements({
      amountAtomic: '50000',  // $0.05 USDC
      resourceUrl: req.originalUrl,
    });
    res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
    return res.status(402).json({});
  }

  const result = await server.settlePayment(paymentSig);
  if (!result.success) {
    return res.status(402).json({ error: result.errorReason });
  }

  res.json({ data: 'protected content' });
});

Legacy capabilities

Several v1-era helpers ship with @deprecated markers in 3.9. They keep working. The markers exist to steer new code at the canonical paths. Each has a JSDoc pointing at its migration target.

| Symbol | Migration target | |---|---| | wrapFetch (@dexterai/x402/client) | payAndFetch (version-agnostic, discriminated return type) | | createX402Client (@dexterai/x402/client) | payAndFetch | | x402AccessPass, useAccessPass | No replacement. Per-request x402Middleware + payAndFetch covers the same usage pattern. | | createDynamicPricing, createTokenPricing, MODEL_PRICING | Price requests in your handler (use your model provider's live API for LLM cases) and pass the amount to x402Middleware. The v1 character-based and tiktoken-based helpers were stopgaps before x402 v2 dynamic pricing landed. | | stripePayTo | No replacement in the SDK. Integrate Stripe at your application layer if needed. | | x402BrowserSupport | No replacement. Build a custom paywall page if you need one. |

Removal release is TBD. Decided after we see how 3.9 deprecation warnings land with real consumers. None of these will be removed in 3.x.


API reference

payAndFetch(url, init, wallets, opts) → Promise<PayResult>

| Argument | Type | Description | |---|---|---| | url | string | Endpoint to fetch | | init | RequestInit | Standard fetch init. Body must be a string. | | wallets | WalletSet | { solana?, evm? }. The SDK picks the chain by what the merchant accepts and what you can pay | | opts | PayAndFetchOptions | maxAmountAtomic, timeoutMs, solanaRpcUrl |

PayResult is a discriminated union. Narrow on ok first, then on paid:

if (result.ok && result.paid) {
  result.response;       // the merchant's response
  result.amountPaid;     // amount actually paid, in the token's smallest denomination
  result.network;        // NetworkRef { caip2, bare, family }
  result.txSignature;    // optional; tx hash where the chain reports one
} else if (result.ok && !result.paid) {
  result.response;       // the merchant didn't demand payment; pass-through
} else {
  result.reason;         // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
  result.detail;         // verbatim merchant error for settlement_failed
}

x402Middleware(config)

| Option | Type | Required | Description | |---|---|---|---| | payTo | string \| { 'solana:*'?, 'eip155:*'?, [caip2]? } | Yes | Receiver address; map for per-chain receivers | | amount | string | Yes | USD amount, e.g., '0.01' | | network | string \| string[] | No | CAIP-2 network(s). Default: Solana mainnet | | description | string | No | Human-readable description | | scheme | 'exact' \| 'batch-settlement' | No | Use 'batch-settlement' to mount as a batch-settlement seller | | extensions | ResourceServerExtension[] | No | E.g., [bazaarExtension()] | | declarations | Record<string, unknown> | No | Per-route extension config (see declareDiscoveryExtension) | | sponsoredAccess | boolean \| { inject?, onMatch? } | No | Enable Instinct ad-network recommendation injection | | facilitatorUrl | string | No | Override facilitator (default: x402.dexter.cash) | | verbose | boolean | No | Debug logging |

useX402Payment({ wallets })

Returns { fetch, isLoading, status, error, transactionId, transactionUrl, balances, refreshBalances, reset, sponsoredRecommendations }. Accepts wallets directly from @solana/wallet-adapter-react and wagmi, with no manual adapter wrapping.

createBatchSettlementSeller(config)

| Option | Type | Description | |---|---|---| | payTo | string | EVM receiver | | network | string | CAIP-2 network | | price | string | Per-call USD price | | storage | ChannelStorage | Optional. Defaults to file storage under ~/.dexter-x402/channels |

Returns an Express handler with .stop(), .closeAll(), .closeChannel(channelId).

bazaarExtension() / declareDiscoveryExtension(config)

The bazaar extension factory takes no arguments. Per-route discovery config is supplied through declareDiscoveryExtension(config):

| Field | Type | Notes | |---|---|---| | method | 'GET' \| 'HEAD' \| 'DELETE' \| 'POST' \| 'PUT' \| 'PATCH' | Optional. If omitted, the actual request method is used. | | queryParams | Record<string, ParamSpec> | For GET/HEAD/DELETE routes | | bodyType | 'json' \| 'form' | For POST/PUT/PATCH routes | | body | Record<string, ParamSpec> | For POST/PUT/PATCH routes | | inputSchema | JSON Schema (Draft 2020-12) | Validates info | | output | { example, schema? } | Example response payload |


Development

npm run build      # ESM + CJS
npm run dev        # Watch mode
npm run typecheck
npm test           # 273 vitest tests

License

MIT. See LICENSE.