New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@suverselabs/x402-client

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@suverselabs/x402-client

Unified buyer SDK for the x402 payment protocol. One client, one config — pays any seller across 18 EVM mainnets, Cosmos Noble, TRON, Solana, and the testnets behind them. Wraps the suverse-pay facilitator by default.

latest
Source
npmnpm
Version
0.1.0
Version published
Weekly downloads
1
-80%
Maintainers
1
Weekly downloads
 
Created
Source

@suverselabs/x402-client

npm license

Unified buyer-side SDK for the x402 payment protocol. One client, one config — pays any seller across 23 networks spanning four virtual machines: EVM (18 mainnets + 3 testnets), Solana, Cosmos Noble, and TRON.

import { SuverseClient } from "@suverselabs/x402-client";

const client = new SuverseClient({
  wallets: { evm: process.env.PRIVATE_KEY as `0x${string}` },
});

const { data, payment } = await client.fetch(
  "https://agentos.suverse.io/v1/freight/parse_ratecon",
  { method: "POST", body: JSON.stringify({ text: "..." }) },
);

console.log(payment.network, payment.txHash, payment.amount);
// → "eip155:8453"  "0x82f4..."  "70000"

The client handles 402 challenges automatically: pick the cheapest network the seller accepts that intersects with the wallets you configured, sign the right payload for that VM, retry. You write client.fetch(url); the multi-chain plumbing is gone.

Install

npm install @suverselabs/x402-client

Requires Node 20+. Runtime deps: viem (EVM + TRON typed-data), @solana/web3.js + @solana/spl-token (SVM), @cosmjs/{crypto,encoding,amino} (Cosmos), bs58 + bs58check (base58/base58check codecs).

Supported networks (v0.1.0)

EVM (18 mainnets + 3 testnets) — scheme: "exact" (EIP-3009)

The same private key signs for every chain in this table. EIP-712 name/version/verifyingContract per chain are vendored from on-chain eth_call name() / version() probes — see chains.ts.

chainCAIP-2USDC contractnotes
Baseeip155:84530x833589fC…02913recommended for first integration
Optimismeip155:100x0b2C639c…0Ff85
Arbitrumeip155:421610xaf88d065…e5831
Polygoneip155:1370x3c499c54…c3359gas in MATIC
World Chaineip155:4800x79A02482…761d4d1
Avalanche C-Chaineip155:431140xB97EF9Ef…6Bc66Dd9c48a6E
Celoeip155:422200xcebA9300…32118C
Lineaeip155:591440x176211869…2821ee1ff
Inkeip155:570730x2D270e688…AEd
XDCeip155:500xfA2958CB…eb1
Monadeip155:1430x754704Bc…603
Soniceip155:1460x29219dd4…894
Seieip155:13290xe15fc38f…392
Abstracteip155:27410x84A71ccD…e1Bridged USDC (Stargate)
IoTeXeip155:46890xcdf79194…42Bridged USDC
Ethereumeip155:10xA0b86991…eB48L1, expensive — last by cost rank
Base Sepoliaeip155:845320x036CbD53…7etestnet
Arbitrum Sepoliaeip155:4216140x75faf114…dtestnet
Avalanche Fujieip155:431130x5425890298…1Bc65testnet

Excluded chains (signer refuses with chain_not_eip3009):

  • BNB Chain (56) — Binance-Peg USDC is 18-decimal EIP-2612 permit, not EIP-3009. Use BofAI's permit pathway through the seller's facilitator.
  • Tempo (4217) — version() reverts on the deployed USDC; settle via MPP / Stripe instead.

Solana — scheme: "exact" (SPL transferChecked)

networkCAIP-2tokens
Mainnetsolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpUSDC (EPjFWdd5…Dt1v), USDT (Es9vMFrz…NYB)
Devnetsolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1USDC test (4zMMC9srt…cDU)

Buyer partial-signs a VersionedTransaction (ComputeBudget×2 + transferChecked + Memo). The facilitator co-signs as feePayer and submits — buyer never spends SOL.

Cosmos — scheme: "exact_cosmos_authz" (ADR-036)

networkCAIP-2denombech32 prefix
Noble mainnetcosmos:noble-1uusdc (6dp)noble
Noble testnetcosmos:grand-1uusdc (6dp)noble

Pre-condition: the payer must run MsgGrant{SendAuthorization} on-chain to the facilitator grantee BEFORE any payment can verify. The signer just produces the signed authorization; the facilitator queries the grant at verify time and rejects without one.

TRON — scheme: "exact_gasfree" ONLY in v0.1.0 ⚠️ experimental

networkCAIP-2tokens
Mainnettron:mainnetUSDT (TR7NHqje…Lj6t)
Nile testnettron:nileUSDT (TXYZopYR…NeBf)

The buyer signs a TIP-712 PermitTransfer authorisation that gasfree.io's relayer executes, paying gas in USDT on the buyer's behalf.

Constraints:

  • $1.50 USDT minimum (gasfree.io relayer rejects below this).
  • gasfree.io contract address is a placeholder by default — pass signerOptions.tron.gasfreeDomain.{mainnet,nile} with the real verifyingContract before production. The signer refuses to sign against the placeholder.
  • exact + exact_permit schemes NOT in v0.1.0 — Tether USDT on TRON doesn't expose EIP-3009 or EIP-2612. Routing layer filters TRON candidates to exact_gasfree only; if the seller advertises ONLY exact on TRON, the client throws NoSupportedNetworkError.

Multi-chain example

import { SuverseClient } from "@suverselabs/x402-client";

const client = new SuverseClient({
  wallets: {
    evm: process.env.EVM_PRIVATE_KEY as `0x${string}`,
    solana: process.env.SOLANA_SECRET_BASE58!,       // bs58(64-byte secret)
    cosmos: process.env.COSMOS_MNEMONIC!,             // 12 or 24 BIP-39 words
    tron: process.env.TRON_PRIVATE_KEY as `0x${string}`,
  },
  preferences: {
    preferredNetwork: "cosmos:noble-1",    // try Cosmos first
    avoidNetworks: ["eip155:1"],            // skip Ethereum L1
  },
  signerOptions: {
    solana: { rpcEndpoint: "https://your-helius-endpoint.com" },
    tron: {
      gasfreeDomain: {
        mainnet: {
          name: "GasFree",
          version: "V1.0.0",
          chainId: 728126428,
          verifyingContract: "0xYOUR_GASFREE_CONTRACT",
        },
      },
    },
  },
});

const { data, payment } = await client.fetch(
  "https://agentos.suverse.io/v1/freight/parse_ratecon",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text: "rate confirmation contents..." }),
  },
);

console.log(`paid on ${payment.network} for ${payment.amount}`);
console.log(`tx: ${payment.txHash}`);
console.log("response:", data);

Network selection

Selection algorithm (when multiple options are viable):

  • Filter sellers' accepts against:
    • which wallets you configured (wallets.*)
    • avoidNetworks blacklist
    • per-network feasibility (EIP-3009 capability for EVM, exact_gasfree for TRON, $1.50 minimum on TRON, etc.)
  • If preferredNetwork is in the surviving set → take it.
  • Otherwise rank by cost class and take the cheapest:
    • cosmos:noble-1 (sub-cent gas)
    • solana:* (sub-cent gas)
    • tron:* (relayer-sponsored, but ≥$1.50)
    • EVM L2 (Base, Arbitrum, Optimism, …)
    • EVM L1 (Ethereum) — multi-dollar gas
    • Testnets — last resort

API reference

new SuverseClient(options)

interface SuverseClientOptions {
  wallets: {
    evm?: `0x${string}` | viem.LocalAccount;    // all 18 EVM mainnets + testnets
    solana?: string | Uint8Array;                // base58 secret OR seed bytes
    cosmos?: string | Uint8Array;                // BIP-39 mnemonic OR 32-byte privkey
    tron?: `0x${string}`;                        // EVM-style hex privkey
  };
  defaultFacilitator?: string;                   // default https://facilitator.suverse.io
  preferences?: {
    preferredNetwork?: string;
    avoidNetworks?: string[];
    maxGasUsd?: number;
  };
  fetchImpl?: typeof fetch;                      // test injection
  signerOptions?: {
    solana?: { rpcEndpoint?: string; computeUnitPriceMicroLamports?: number; ... };
    cosmos?: { validitySeconds?: number };
    tron?: { gasfreeDomain?: { mainnet?: GasfreeDomain; nile?: GasfreeDomain }; ... };
  };
}

client.fetch<T>(url, init?)

Drop-in replacement for fetch. On a 200 response, returns the body verbatim. On a 402, parses the challenge, picks a network, signs, retries with PAYMENT-SIGNATURE + X-PAYMENT headers attached.

const { data, response, payment } = await client.fetch<MyResponseType>(url, init);

payment is a PaymentReceipt:

interface PaymentReceipt {
  network: string;       // e.g. "eip155:8453"
  scheme: string;        // e.g. "exact"
  asset: string;         // contract / mint / denom
  amount: string;        // atomic units
  payer: string;         // buyer's address in network-native format
  payTo: string;         // seller's address
  txHash: string | null; // null when verify-only or PAYMENT-RESPONSE absent
}

client.pay(challenge, prefs?) / client.signFor(challenge, prefs?)

When you handle the HTTP yourself and want just the header value:

const challenge = await parseSellerChallenge(response);
const headerValue = await client.pay(challenge);

const retry = await fetch(url, {
  headers: { "X-Payment": headerValue, "PAYMENT-SIGNATURE": headerValue },
});

.pay() and .signFor() are aliases; pick whichever reads better.

client.signRequirement(requirement, options?)

When you've already picked the network and just want the envelope:

const envelope = await client.signRequirement(requirement, {
  resource: "https://api.seller/paid",  // REQUIRED for Cosmos
});

The resource option is required for Cosmos networks (the URL is part of the signed preimage). EVM / Solana / TRON ignore it.

Lower-level signing

Each VM signer is exported standalone for advanced use:

import { EvmSigner, toHeaderValue as evmHeader } from "@suverselabs/x402-client/evm";
import { SolanaSigner } from "@suverselabs/x402-client/solana";
import { CosmosSigner, adr036Preimage } from "@suverselabs/x402-client/cosmos";
import { TronSigner } from "@suverselabs/x402-client/tron";

const signer = new EvmSigner({ wallet: "0x..." });
const envelope = await signer.sign({ requirement });
const header = evmHeader(envelope);

Subpath exports keep the bundle small if you only need one VM.

Error catalog

All errors extend X402ClientError (which extends Error) and carry a .code string. Common subclasses:

import {
  X402ClientError,
  NoSupportedNetworkError,
  InsufficientAmountError,
  FacilitatorRejectedError,
} from "@suverselabs/x402-client";
codethrown bywhat it meansfix
unexpected_status.fetch()seller returned non-200 / non-402check seller URL + try again
payment_retry_failed.fetch()seller still rejected after signed retryinspect retry's response body for the seller's reason
invalid_challenge.fetch() / parser402 body or PAYMENT-REQUIRED header malformedupgrade seller's middleware or contact them
empty_challengeroutingaccepts array emptyseller bug; not actionable client-side
no_evm_wallet / no_solana_wallet / no_cosmos_wallet / no_tron_wallet.fetch()seller requires VM family X, you didn't pass wallets.xadd the wallet to your config
missing_resource.signRequirement()Cosmos signer needs options.resourcepass challenge.resource.url (or use .fetch() / .signFor())
unsupported_chaineach signernetwork not in the SDK's registryopen an issue + pin a release that adds it
unsupported_network_familyclientnetwork is near:* / aptos:* etcuse a different VM family
scheme_mismatchEVM / Solana / Cosmoschallenge scheme isn't what the signer handlescheck seller's challenge.scheme
chain_not_eip3009EVMBNB Chain / Tempo / similarroute through a Permit2 pathway instead
not_evm_networkEVMEvmSigner.sign called with non-eip155:*use the right signer
domain_mismatchEVMseller's extra.{name,version} disagrees with the trusted local USDC domainflag the seller — possible spoof
asset_mismatchEVMrequirement.asset isn't the canonical USDC contract for that chainseller bug or wrong contract
chain_id_mismatchCosmosextra.chainId disagrees with our registryseller bug
missing_facilitatorCosmosextra.facilitator missing (grantee bech32)seller bug
missing_fee_payerSolanaextra.feePayer missingseller bug
fee_payer_collisionSolanafeePayer equals source authority or ATA — spec violationseller bug
unknown_tokenTRON / Solanamint / contract isn't in the SDK token registryadd extra.decimals (Solana) or open an issue (TRON)
unknown_decimalsSolanamint not in registry AND extra.decimals not setseller should set extra.decimals
memo_too_longSolanaseller's extra.memo > 256 bytesseller bug
missing_gasfree_domainTRONplaceholder verifyingContract not overriddenpass real signerOptions.tron.gasfreeDomain.{mainnet,nile}
scheme_not_implemented_v0_1_0TRONseller advertised TRON exact or exact_permituse exact_gasfree or wait for a later release
invalid_walletevery signerwallet format wrong for this VMre-check the shape — see types in SuverseClientOptions
invalid_validityevery signervaliditySeconds ≤ 0use a positive integer
invalid_cu_priceSolanaCU price out of [1, 5_000_000]see Solana spec cap
invalid_max_fee / invalid_pubkey / invalid_tron_address / invalid_evm_addressvariousmalformed address / valuesanity-check inputs
blockhash_fetch_failedSolanaRPC unreachableset signerOptions.solana.rpcEndpoint
no_supported_networkroutingintersection of seller's accepts ∩ your wallets ∩ avoidNetworks is emptyadd a wallet, drop avoidNetworks, or check seller's offering
insufficient_amountTRONseller asks for less than gasfree.io's $1.50 USDT minimumthis challenge is unfulfillable on TRON; use another VM if seller offers one
facilitator_rejected(future)reserved for catching facilitator-side 4xxinspect .invalidReason

Every error message also includes a human-readable hint pointing at the most likely fix. .code is the stable identifier — match on it rather than the message text in production code.

Tests

The package ships 165 tests (8 suites + 1 live-gated suite):

pnpm test                           # 163 unit tests, deterministic
SUVERSE_LIVE=1 pnpm test            # also runs 2 live probes against facilitator.suverse.io

The live probe sends a signed EVM envelope to /facilitator/verify and asserts the facilitator → Coinbase CDP path doesn't reject at the schema level. It's gated because it needs internet + Base mainnet reachability; default unit runs stay offline + reproducible.

License

Apache-2.0

Keywords

x402

FAQs

Package last updated on 30 May 2026

Related posts