@paygate/core
The chain-agnostic foundation of the @paygate library. Provides the full x402 payment flow, shared TypeScript types, and the abstract base class that all chain-specific clients extend.
Installation
npm install @paygate/core
# or
pnpm add @paygate/coreWhat This Package Provides
BaseX402Client- abstract class that handles the entire x402 HTTP flowfetchWithPayment- drop-in replacement forfetchthat handles402responses automatically- Shared types -
PaymentRequirements,SettlementResponse,BaseX402Payload - Utility helpers -
needsPayment,getPaymentRequirements,parseSettlementResponse - REST + JSON-RPC support - handles both HTTP 402 and JSON-RPC error code 402
Core Types
PaymentRequirements
The payment instructions returned by a resource server in a 402 response.
interface PaymentRequirements {
scheme: string; // "exact"
network: string; // e.g. "base-sepolia", "osmosis"
maxAmountRequired: string; // atomic units, e.g. "1000000" = 1 USDC
resource: string; // URL path being paid for
description: string; // human-readable description
mimeType?: string; // expected response MIME type
payTo: string; // recipient wallet address
asset: string; // token contract / mint / denom
maxTimeoutSeconds: number; // seconds before the authorization expires
extra?: Record<string, any>; // chain-specific extras (chainId, feePayer, etc.)
}PaymentRequirementsResponse
The body of a 402 HTTP response. Supports two formats:
// Format 1 - single requirement
{ paymentRequirements: PaymentRequirements }
// Format 2 - list of accepted options
{ accepts: PaymentRequirements[] }SettlementResponse
Returned in the X-PAYMENT-SETTLEMENT header (base64-encoded JSON) after successful settlement.
interface SettlementResponse {
success: boolean;
errorReason?: string;
transaction: string; // on-chain tx hash
network: string;
payer: string;
}FetchWithPaymentOptions
Extends the standard RequestInit with lifecycle callbacks.
interface FetchWithPaymentOptions extends RequestInit {
onPaymentRequired?: (requirements: PaymentRequirements) => Promise<boolean>;
onSigningRequired?: () => void;
onPaymentComplete?: (settlement?: SettlementResponse) => void;
}BaseX402Payload
The structure placed in the X-PAYMENT header.
interface BaseX402Payload {
x402Version: number;
paymentRequirements: PaymentRequirements;
paymentPayload: {
x402Version: number;
scheme: string;
network: string;
payload: string; // base64-encoded chain-specific payload
signature: string; // base64-encoded signature
};
}BaseX402Client
Abstract base class. Implement two methods; everything else is handled for you.
abstract class BaseX402Client {
// YOU IMPLEMENT: sign a payment and return base64 payload
protected abstract createPaymentPayload(
requirements: PaymentRequirements
): Promise<string>
// YOU IMPLEMENT: return the current wallet address
abstract getAccount(): Promise<{ address: string; [key: string]: any }>
// BUILT-IN: handles the full x402 flow
async fetchWithPayment(
url: string,
options?: FetchWithPaymentOptions
): Promise<Response>
}Payment Flow (implemented by fetchWithPayment)
- Make initial
fetch(url). - If
200- return response directly (no payment needed). - If
402- parsePaymentRequirementsfrom the response body. - Call
onPaymentRequiredcallback (if provided); if it returnsfalse, abort. - Call
onSigningRequiredcallback (if provided). - Call
createPaymentPayload(requirements)- your chain-specific implementation. - Retry the request with
X-PAYMENT: <base64-payload>header. - Parse
X-PAYMENT-SETTLEMENTfrom the response headers and callonPaymentComplete. - Return the final response.
Also handles JSON-RPC 402: if the response body contains { error: { code: 402, data: { data: PaymentRequirements } } }, the same flow runs.
Usage
Basic
import { EthereumX402Client } from '@paygate/client-ethereum'
const client = new EthereumX402Client({ ... })
const response = await client.fetchWithPayment('https://api.example.com/data')
const data = await response.json()With Callbacks
const response = await client.fetchWithPayment('https://api.example.com/data', {
onPaymentRequired: async (requirements) => {
console.log(`Payment of ${requirements.maxAmountRequired} required`)
// Show confirmation dialog, return false to cancel
return confirm('Approve payment?')
},
onSigningRequired: () => {
showLoadingSpinner('Waiting for wallet signature...')
},
onPaymentComplete: (settlement) => {
hideLoadingSpinner()
if (settlement?.success) {
console.log('Settled on-chain:', settlement.transaction)
}
},
})With Standard Fetch Options
fetchWithPayment accepts all standard fetch options (method, headers, body, etc.):
const response = await client.fetchWithPayment('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'SELECT * FROM table' }),
})Utility Functions
import { needsPayment, getPaymentRequirements, parseSettlementResponse } from '@paygate/core'
// Check if a response requires payment
if (needsPayment(response)) { ... }
// Extract requirements from a 402 response
const requirements = await getPaymentRequirements(response)
// Parse the settlement header from a paid response
const settlement = parseSettlementResponse(paidResponse)Building a Custom Chain Client
Implement BaseX402Client to support any blockchain:
import { BaseX402Client, type PaymentRequirements } from '@paygate/core'
class MyChainClient extends BaseX402Client {
async getAccount() {
return { address: 'my-wallet-address' }
}
protected async createPaymentPayload(
requirements: PaymentRequirements
): Promise<string> {
// 1. Build a transaction from requirements
// 2. Sign with your wallet
// 3. Return base64-encoded x402 payload
const payload = { ... }
return Buffer.from(JSON.stringify(payload)).toString('base64')
}
}