x402 SDK

Use the OptimAI x402 SDK to create paid searches and keep access across agents.

@optimai-network/x402-sdk is a typed client SDK for OptimAI x402 search endpoints.

The current package requires Node.js 20.18.0 or newer.

It wraps two awkward parts of the flow:

  • the initial 402 Payment Required challenge
  • the follow-up payment-signature header generation

It also returns a serializable paymentContext object, including the original payment-signature, so another agent or process can keep working with the same paid search later.

Install

pnpm add @optimai-network/x402-sdk

Quick start: EVM / Base

import {
  createOptimaiX402Client,
  createViemPaymentHandler,
} from "@optimai-network/x402-sdk"

const paymentHandler = createViemPaymentHandler({
  privateKey: process.env.X402_EVM_PAYER_PRIVATE_KEY!,
  rpcUrls: {
    "eip155:8453": "https://mainnet.base.org",
    "eip155:84532": "https://sepolia.base.org",
  },
})

const client = createOptimaiX402Client({
  baseUrl: "https://api-onchain.optimai.network",
  paymentHandler,
})

const { search, paymentContext } = await client.createSearch({
  query: "What is liquidation?",
  search_mode: "agent",
})

const completed = await client.waitForSearchCompletion(search.id, {
  paymentContext,
})

console.log(completed.result?.answer)

Search modes and pricing

Omit search_mode or set it to "search" for Standard Search. Set it explicitly to "agent" for longer multi-step web, social, and crypto research. Agent mode is text-only.

The server advertises the exact payment amount in its 402 Payment Required challenge. Every configured Agent-mode offer is exactly 2 times its Standard Search offer. Clients should always sign the amount from the challenge instead of hard-coding a price.

Search mode is part of idempotency. Retrying an identical request with the same idempotency key is safe, but changing between Standard and Agent mode requires a new key.

The x402 API currently supports Search only. Use API-key authentication for Scrape, Seed, and Crawl.

X402_EVM_PAYER_PRIVATE_KEY is the payer wallet private key used to sign the x402 payment payload. It is not a server key, not a Coinbase facilitator key, and not an API key.

Quick start: Solana

import {
  createOptimaiX402Client,
  createSolanaPaymentHandler,
} from "@optimai-network/x402-sdk"

const paymentHandler = await createSolanaPaymentHandler({
  privateKey: process.env.X402_SOLANA_PAYER_PRIVATE_KEY!,
  rpcUrls: {
    "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "https://api.mainnet-beta.solana.com",
    "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "https://api.devnet.solana.com",
  },
})

const client = createOptimaiX402Client({
  baseUrl: "https://api-onchain.optimai.network",
  paymentHandler,
})

const { search, paymentContext } = await client.createSearch({
  query: "What is liquidation?",
})

const completed = await client.waitForSearchCompletion(search.id, {
  paymentContext,
})

console.log(completed.result?.answer)

X402_SOLANA_PAYER_PRIVATE_KEY is the Solana payer wallet private key used to sign the x402 payment payload. The payer wallet needs enough USDC for the payment and enough SOL to pay Solana network fees.

Client methods

createOptimaiX402Client(config) creates the high-level SDK client.

Available methods:

  • createSearch(input, options?)
  • getSearch(id, options?)
  • getSearchWithPaymentContext(id, options?) (returns the latest Search and payment context)
  • getSearchResult(id, options?) (alias for getSearchWithPaymentContext)
  • cancelSearch(id, options?)
  • waitForSearchCompletion(id, options?)
  • waitForSearchCompletionWithPaymentContext(id, options?)
  • rememberPaymentContext(context)
  • forgetPaymentContext(id)

Payment handlers

createViemPaymentHandler(config) creates a payment handler backed by @x402/evm, @x402/fetch, and viem.

Built-in network support:

CAIP-2Network
eip155:8453Base
eip155:84532Base Sepolia

createSolanaPaymentHandler(config) creates a payment handler backed by the Solana x402 path.

Built-in Solana network support:

CAIP-2Network
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpSolana mainnet
solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1Solana devnet

Payment requirements and payTo

SDK users do not manually set payTo, amount, asset, network, or Solana feePayer values in normal usage. Those values come from the OptimAI server's 402 Payment Required challenge.

The SDK reads the server challenge, chooses the first supported payment option for the handler you configured, signs the payment, and retries the paid request. If your app needs to display the receiver or payment details, inspect paymentContext.paymentRequired.accepts after createSearch().

Payment statuses

x402_payment_status in responses can be:

StatusMeaning
verified_unsettledPayment is verified, but settlement is not finalized in OptimAI yet. A chain transfer may already exist while the server is still confirming and persisting the settlement ID.
settlement_unconfirmedLegacy name accepted for compatibility with older server responses. Treat it like verified_unsettled.
settledSettlement is finalized and the settlement ID, usually a transaction hash, is persisted.
voidedPayment was voided, usually because the request was cancelled or failed before settlement.
settlement_failedThe server exhausted settlement retries and needs manual or operator follow-up.

Result-bound settlement

The server settles payment only after Search completes with a usable, non-empty result. Failed, cancelled, timed-out, or empty-result searches are voided.

Final result access uses a fresh result-bound authorization. Keep and pass the SDK's paymentContext; the SDK handles this follow-up proof without extending or reusing an expired initial authorization.

When you need to persist access across workers, prefer the WithPaymentContext methods. They return both the Search response and the latest context, so you can store the rotated result-bound signature after a completed Search. A response can briefly be completed without a final result while payment is still verified_unsettled (or the legacy settlement_unconfirmed); continue polling with the returned context until the result is available or the operation is voided/failed.

Persist access across agents

GET /external/v1/x402/search/:id works best when the client replays the exact original payment-signature used for the paid create call.

Because of that, the SDK returns:

type SearchPaymentContext = {
  id: string
  paymentRequired: X402PaymentRequired
  paymentSignature?: string
}

Persist this object alongside the search ID if another agent or process will poll or fetch the search later:

const { search, paymentContext } = await client.createSearch({ query: "..." })

// Store paymentContext somewhere durable.

client.rememberPaymentContext(paymentContext)
const latest = await client.getSearch(search.id)

If you retry createSearch() with the same Idempotency-Key from another process, pass the previously stored context back in options.existingPaymentContext so the SDK can authenticate the 303 redirect target with the original signature:

const retry = await client.createSearch(
  { query: "..." },
  {
    idempotencyKey: "search-123",
    existingPaymentContext: paymentContext,
  }
)

Local smoke tests

The package includes a smoke script for testing the built SDK against a live OptimAI x402 endpoint:

X402_EVM_PAYER_PRIVATE_KEY=0x... pnpm smoke:local:evm

For backward compatibility, pnpm smoke:local still runs the EVM smoke path.

Solana smoke testing is optional and additive:

X402_SOLANA_PAYER_PRIVATE_KEY='[...]' pnpm smoke:local:solana

Defaults:

  • OPTIMAI_X402_BASE_URL=https://api-onchain.optimai.network
  • mainnet RPC for eip155:8453 defaults to https://mainnet.base.org
  • Solana mainnet RPC defaults to https://api.mainnet-beta.solana.com

Optional overrides:

  • OPTIMAI_X402_QUERY="What is liquidation?"
  • OPTIMAI_X402_BASE_URL=http://localhost:3002
  • X402_EVM_BASE_RPC_URL=https://mainnet.base.org
  • X402_EVM_BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
  • X402_SOLANA_MAINNET_RPC_URL=https://api.mainnet-beta.solana.com
  • X402_SOLANA_DEVNET_RPC_URL=https://api.devnet.solana.com
  • X402_PAYER_PRIVATE_KEY=0x... for legacy EVM setups
  • X402_RPC_URL_BASE=https://mainnet.base.org
  • X402_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org
  • X402_RPC_URL=https://mainnet.base.org
  • OPTIMAI_X402_RUN_CANCEL=1