Skip to content

x402 Compatibility

Bidirectional interop between s402 and x402. An x402 client can talk to an s402 server (via the "exact" scheme), and an s402 client can talk to an x402 server (via automatic normalization).

typescript
import {
  fromX402Requirements,
  toX402Requirements,
  normalizeRequirements,
  isS402,
  isX402,
} from 's402/compat';

Why This Exists

x402 (by Coinbase) established HTTP 402 payments. s402 extends the protocol for Sui-native capabilities — but maintains wire compatibility so the ecosystem isn't fragmented. An x402 client that only knows "exact" payments can talk to an s402 server without any changes.

Auto-Detection

normalizeRequirements(obj)

The recommended entry point. Auto-detects the protocol format and normalizes to s402.

typescript
function normalizeRequirements(
  obj: Record<string, unknown>,
): s402PaymentRequirements;

Handles three formats:

  1. s402 — validates and passes through
  2. x402 V1 — flat object with x402Version, scheme, maxAmountRequired
  3. x402 V2 — envelope with accepts array

Example:

typescript
// Works regardless of whether the server speaks s402 or x402
const raw = JSON.parse(atob(response.headers.get('payment-required')));
const requirements = normalizeRequirements(raw);
// Always returns s402PaymentRequirements

isS402(obj) / isX402(obj)

Quick checks for protocol format.

typescript
function isS402(obj: Record<string, unknown>): boolean;  // has s402Version
function isX402(obj: Record<string, unknown>): boolean;  // has x402Version, no s402Version

isX402Envelope(obj)

Detect x402 V2 envelope format (has x402Version + accepts array).

typescript
function isX402Envelope(obj: Record<string, unknown>): boolean;

x402 → s402

fromX402Requirements(x402)

Convert x402 requirements to s402 format.

typescript
function fromX402Requirements(
  x402: x402PaymentRequirements,
): s402PaymentRequirements;
  • Maps schemeaccepts: ['exact']
  • Handles both V1 (maxAmountRequired) and V2 (amount) wire formats
  • Preserves extra field for forward compatibility

fromX402Payload(x402)

Convert an x402 payment payload to s402 format.

typescript
function fromX402Payload(x402: x402PaymentPayload): s402ExactPayload;

fromX402Envelope(envelope)

Convert an x402 V2 envelope to s402 format. Picks the first requirement from the accepts array.

typescript
function fromX402Envelope(
  envelope: x402PaymentRequiredEnvelope,
): s402PaymentRequirements;

s402 → x402

toX402Requirements(s402)

Convert s402 requirements to x402 V1 wire format. Strips s402-only fields (mandate, stream, escrow, seal, prepaid extras).

typescript
function toX402Requirements(
  s402: s402PaymentRequirements,
): x402PaymentRequirements;

Includes both maxAmountRequired (V1) and amount (V2) for maximum compatibility. Sets maxTimeoutSeconds: 60 (required by x402).

toX402Payload(s402)

Convert s402 payload to x402 format. Only works for "exact" scheme — other schemes have no x402 equivalent.

typescript
function toX402Payload(
  s402: s402PaymentPayload,
): x402PaymentPayload | null;

Returns null if the scheme is not "exact".

x402 Types

Exported for consumers who need to work with x402 data directly:

typescript
import type {
  x402PaymentRequirements,
  x402PaymentRequiredEnvelope,
  x402PaymentPayload,
} from 's402/compat';

x402PaymentRequirements

typescript
interface x402PaymentRequirements {
  x402Version: number;
  scheme: string;
  network: string;
  asset: string;
  amount?: string;              // V2
  maxAmountRequired?: string;   // V1
  payTo: string;
  maxTimeoutSeconds?: number;
  resource?: string;            // V1 only
  description?: string;         // V1 only
  facilitatorUrl?: string;
  extra?: Record<string, unknown>;
}

x402PaymentRequiredEnvelope

typescript
interface x402PaymentRequiredEnvelope {
  x402Version: number;
  accepts: x402PaymentRequirements[];
  resource?: { url?: string; mimeType?: string; description?: string };
  extensions?: Record<string, unknown>;
  error?: string;
}

Released under the MIT License.