
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@x402/core
Advanced tools
@x402/core • Core implementation of the x402 payment protocol for TypeScript/JavaScript applications. Provides transport-agnostic client, server and facilitator components.
pnpm install @x402/core
import { x402Client } from '@x402/core/client';
import { x402HTTPClient } from '@x402/core/http';
import { ExactEvmScheme } from '@x402/evm/exact/client';
// Create core client and register payment schemes
const coreClient = new x402Client()
.register('eip155:*', new ExactEvmScheme(evmSigner));
// Wrap with HTTP client for header encoding/decoding
const client = new x402HTTPClient(coreClient);
// Make a request
const response = await fetch('https://api.example.com/protected');
if (response.status === 402) {
// Extract payment requirements from response
const paymentRequired = client.getPaymentRequiredResponse(
(name) => response.headers.get(name),
await response.json()
);
// Create and send payment
const paymentPayload = await client.createPaymentPayload(paymentRequired);
const paidResponse = await fetch('https://api.example.com/protected', {
headers: client.encodePaymentSignatureHeader(paymentPayload),
});
// Get settlement confirmation
const settlement = client.getPaymentSettleResponse(
(name) => paidResponse.headers.get(name)
);
console.log('Transaction:', settlement.transaction);
}
import { x402ResourceServer, HTTPFacilitatorClient } from '@x402/core/server';
import { x402HTTPResourceServer } from '@x402/core/http';
import { ExactEvmScheme } from '@x402/evm/exact/server';
// Connect to facilitator
const facilitatorClient = new HTTPFacilitatorClient({
url: 'https://x402.org/facilitator',
});
// Create resource server with payment schemes
const resourceServer = new x402ResourceServer(facilitatorClient)
.register('eip155:*', new ExactEvmScheme());
// Initialize (fetches supported kinds from facilitator)
await resourceServer.initialize();
// Configure routes with payment requirements
const routes = {
'GET /api/data': {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xYourAddress',
price: '$0.01',
},
description: 'Premium data access',
mimeType: 'application/json',
},
};
// Create HTTP server wrapper
const httpServer = new x402HTTPResourceServer(resourceServer, routes);
import { x402Facilitator } from '@x402/core/facilitator';
import { registerExactEvmScheme } from '@x402/evm/exact/facilitator';
const facilitator = new x402Facilitator();
// Register scheme implementations using helper
registerExactEvmScheme(facilitator, {
signer: evmSigner,
networks: 'eip155:84532',
});
// Verify payment
const verifyResult = await facilitator.verify(paymentPayload, paymentRequirements);
if (verifyResult.isValid) {
// Settle payment
const settleResult = await facilitator.settle(paymentPayload, paymentRequirements);
console.log('Transaction:', settleResult.transaction);
}
Routes use the accepts field to define payment options:
const routes = {
// Single payment option
'GET /api/data': {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xAddress',
price: '$0.01',
},
description: 'Data endpoint',
mimeType: 'application/json',
},
// Multiple payment options (EVM + SVM)
'POST /api/*': {
accepts: [
{
scheme: 'exact',
network: 'eip155:8453',
payTo: evmAddress,
price: '$0.05',
},
{
scheme: 'exact',
network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
payTo: svmAddress,
price: '$0.05',
},
],
},
};
Use fromConfig() for declarative setup. Accept selection runs in three stages: spendControls enforce built-in safety caps, policies filter the remaining list, and paymentRequirementsSelector picks one accept (default: first remaining).
const TRUSTED_PAY_TO = '0xYourServerAddress';
const client = x402Client.fromConfig({
schemes: [
{ network: 'eip155:8453', client: new ExactEvmScheme(evmSigner) },
{ network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', client: new ExactSvmScheme(svmSigner) },
],
spendControls: {
maxAmountPerPayment: '$5', // default "$1" on default assets; false to disable
},
policies: [
// Filter: drop accepts that pay an unexpected recipient
(version, reqs) => reqs.filter(r => r.payTo.toLowerCase() === TRUSTED_PAY_TO.toLowerCase()),
],
paymentRequirementsSelector: (version, reqs) =>
// Pick one: prefer Base when still available after filtering
reqs.find(r => r.network === 'eip155:8453') ?? reqs[0],
});
For per-asset caps and asset allowlists, see spend controls below.
Built-in safety rails applied before policies. Use these for amount and asset bounds—not for network preference.
By default only assets findDefaultAsset recognizes are allowed, with a $1 USD ceiling. Opt into other tokens via allowedAssets, or pass spendControls: false to disable all spend controls.
spendControls: {
maxAmountPerPayment: '$5', // USD cap on default assets; false to remove
allowedAssets: [
// opt-in non-default with atomic cap
{ network: 'eip155:8453', asset: '0xCustomToken', maxAmountPerPayment: '2000000' },
// opt-in non-default uncapped
{ network: 'eip155:8453', asset: '0xOtherToken' },
// override USD cap for a default asset by ticker (or on-chain id)
{ network: 'eip155:8453', asset: 'PYUSD', maxAmountPerPayment: '500000' },
],
// or: allowedAssets: true // allow any asset (USD cap still applies to defaults)
},
// or: spendControls: false // disable all spend controls (any asset, no caps)
| Control | Purpose |
|---|---|
spendControls: false | Disable all spend controls (any asset, no caps). Useful for UI-confirmed flows (paywall) and tests. |
maxAmountPerPayment | USD ceiling on payments in recognized USD-pegged assets (default $1). Applies to every default asset the registered scheme's findDefaultAsset knows about. Set a higher Money value to raise the cap, or false to remove it. |
allowedAssets | Opt-in for non-default tokens. Omit for default assets only; true to allow any asset; or a list of { network, asset } (optional integer atomic maxAmountPerPayment per entry, e.g. "2000000", not "$1"). asset may be an onchain id or a default-asset symbol (e.g. "PYUSD"). |
Network scoping is separate: register only the networks you intend to pay on (e.g. registerExactEvmScheme(client, { signer, networks: ['eip155:8453'] })). Unregistered networks are never selected regardless of spend controls.
PaymentPolicy functions shrink the accept list: (version, reqs) => reqs. They run after spend controls and before the selector. Use them for custom exclusion rules (trusted payTo, required scheme, environment-specific filters). Do not use policies for USD caps or asset allowlists—that is what spendControls is for.
SelectPaymentRequirements picks exactly one accept from what policies leave: (version, reqs) => req. Pass it to fromConfig({ paymentRequirementsSelector }) or new x402Client(selector). Default behavior is reqs[0]. Use it for preference and ranking (cheapest option, preferred network, wallet default)—not for hard safety limits.
// Prefer the cheapest remaining accept
paymentRequirementsSelector: (version, reqs) =>
[...reqs].sort((a, b) => (BigInt(a.amount) < BigInt(b.amount) ? -1 : 1))[0],
For interactive approval before signing, use onBeforePaymentCreation hooks instead.
client
.onBeforePaymentCreation(async (ctx) => {
console.log('Creating payment for:', ctx.selectedRequirements.network);
// Return { abort: true, reason: '...' } to cancel
})
.onAfterPaymentCreation(async (ctx) => {
console.log('Payment created:', ctx.paymentPayload);
})
.onPaymentCreationFailure(async (ctx) => {
console.error('Payment failed:', ctx.error);
// Return { recovered: true, payload: ... } to recover
});
resourceServer
.onBeforeVerify(async (ctx) => { /* ... */ })
.onAfterVerify(async (ctx) => { /* ... */ })
.onBeforeSettle(async (ctx) => { /* ... */ })
.onAfterSettle(async (ctx) => { /* ... */ });
facilitator
.onBeforeVerify(async (ctx) => { console.log('Before verify', ctx); })
.onAfterVerify(async (ctx) => { console.log('After verify', ctx); })
.onVerifyFailure(async (ctx) => { console.log('Verify failure', ctx); })
.onBeforeSettle(async (ctx) => { console.log('Before settle', ctx); })
.onAfterSettle(async (ctx) => { console.log('After settle', ctx); })
.onSettleFailure(async (ctx) => { console.log('Settle failure', ctx); });
| Header | Description |
|---|---|
PAYMENT-SIGNATURE | Base64-encoded payment payload |
PAYMENT-REQUIRED | Base64-encoded payment requirements |
PAYMENT-RESPONSE | Base64-encoded settlement response |
| Header | Description |
|---|---|
X-PAYMENT | Base64-encoded payment payload |
X-PAYMENT-RESPONSE | Base64-encoded settlement response |
Register handlers for network families using wildcards:
// All EVM networks
server.register('eip155:*', new ExactEvmScheme());
// Specific network takes precedence
server.register('eip155:8453', new ExactEvmScheme());
type Network = `${string}:${string}`; // e.g., "eip155:8453"
type PaymentRequirements = {
scheme: string;
network: Network;
asset: string;
amount: string;
payTo: string;
maxTimeoutSeconds: number;
extra: Record<string, unknown>;
};
type PaymentPayload = {
x402Version: number;
resource: ResourceInfo;
accepted: PaymentRequirements;
payload: Record<string, unknown>;
extensions?: Record<string, unknown>;
};
type PaymentRequired = {
x402Version: number;
error?: string;
resource: ResourceInfo;
accepts: PaymentRequirements[];
extensions?: Record<string, unknown>;
};
For framework-specific middleware, use:
@x402/express - Express.js middleware@x402/hono - Hono middleware@x402/next - Next.js integration@x402/axios - Axios interceptor@x402/fetch - Fetch wrapperFor blockchain-specific implementations:
@x402/evm - Ethereum and EVM-compatible chains@x402/svm - Solana blockchain@x402/avm - Algorand blockchainSee the examples directory for complete examples.
Contributions welcome! See Contributing Guide.
FAQs
x402 Payment Protocol
The npm package @x402/core receives a total of 177,356 weekly downloads. As such, @x402/core popularity was classified as popular.
We found that @x402/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.