Skip to content

@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

bash
npm install @paygate/core
# or
pnpm add @paygate/core

What This Package Provides

  • BaseX402Client - abstract class that handles the entire x402 HTTP flow
  • fetchWithPayment - drop-in replacement for fetch that handles 402 responses 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.

typescript
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:

typescript
// 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.

typescript
interface SettlementResponse {
  success: boolean;
  errorReason?: string;
  transaction: string;  // on-chain tx hash
  network: string;
  payer: string;
}

FetchWithPaymentOptions

Extends the standard RequestInit with lifecycle callbacks.

typescript
interface FetchWithPaymentOptions extends RequestInit {
  onPaymentRequired?: (requirements: PaymentRequirements) => Promise<boolean>;
  onSigningRequired?: () => void;
  onPaymentComplete?: (settlement?: SettlementResponse) => void;
}

BaseX402Payload

The structure placed in the X-PAYMENT header.

typescript
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.

typescript
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)

  1. Make initial fetch(url).
  2. If 200 - return response directly (no payment needed).
  3. If 402 - parse PaymentRequirements from the response body.
  4. Call onPaymentRequired callback (if provided); if it returns false, abort.
  5. Call onSigningRequired callback (if provided).
  6. Call createPaymentPayload(requirements) - your chain-specific implementation.
  7. Retry the request with X-PAYMENT: <base64-payload> header.
  8. Parse X-PAYMENT-SETTLEMENT from the response headers and call onPaymentComplete.
  9. 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

typescript
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

typescript
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.):

typescript
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

typescript
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:

typescript
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')
  }
}

Released under the MIT License.