X402 TypeScript SDK Integration Tutorial

TypeScript is one of the most recommended ways to integrate with YuJun X402. The official SDK handles regular API calls, task polling, error handling, and automatic retries; @acedatacloud/x402-client is responsible for signing the PAYMENT-SIGNATURE request header when encountering 402 Payment Required.

Source code and package addresses:

Install Dependencies

npm install @acedatacloud/sdk @acedatacloud/x402-client

If using Base or SKALE, EVM signing capability is required:

npm install ethers

If using Solana, a Solana wallet adapter or @solana/web3.js is needed:

npm install @solana/web3.js

Clean npm project installation and import check output:

imports_ok true true true true true
@acedatacloud/sdk@2026.504.2
@acedatacloud/x402-client@2026.531.3
ethers@6.16.0
@solana/web3.js@1.98.4

Result explanation:

  • Both @acedatacloud/sdk and @acedatacloud/x402-client can be installed from npm and imported into Node.js.
  • ethers is used for EVM typed data signing, and @solana/web3.js is used for Solana transaction construction.

Base or SKALE Example

In the browser, window.ethereum can be used directly. In Node.js, ethers.Wallet can wrap an EIP-1193 style provider.

import { Wallet } from 'ethers';
import { AceDataCloud } from '@acedatacloud/sdk';
import { createX402PaymentHandler } from '@acedatacloud/x402-client';

const wallet = new Wallet(process.env.EVM_PRIVATE_KEY!);

const evmProvider = {
  async request({ method, params }: { method: string; params?: unknown[] }) {
    if (method !== 'eth_signTypedData_v4') {
      throw new Error(`unsupported method: ${method}`);
    }
    const [, typedDataJson] = params as [string, string];
    const typedData = JSON.parse(typedDataJson);
    return wallet.signTypedData(typedData.domain, typedData.types, typedData.message);
  }
};

const client = new AceDataCloud({
  paymentHandler: createX402PaymentHandler({
    network: 'base',
    evmProvider,
    evmAddress: wallet.address
  })
});

const result = await client.openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Say hi in 3 words' }],
  max_tokens: 10
});

console.log(result.choices[0].message.content);

The output of this example program:

payer 0xd0479FA9FD8C678303d477433d24C15e3723CC1C
elapsed_ms 6782
content ADC_TS_SDK_X402_OK
id chatcmpl-DlcVLO4PQWvmjPDQpy9yQw2QdLGAT

Result explanation:

  • The program first triggers an unauthenticated 402, then the handler signs the PAYMENT-SIGNATURE, and finally retries with the same request body.
  • content ADC_TS_SDK_X402_OK is a fixed string returned by the model, indicating that the retried request entered the upstream API.
  • id chatcmpl-DlcVLO4PQWvmjPDQpy9yQw2QdLGAT is the response ID for this chat completion, which can be used to cross-reference with platform usage records.
  • On-chain settlement results can be found in E2E Verification and Troubleshooting.

Change network to skale to use SKALE. The advantage of SKALE is the low gas cost for on-chain transactions; the advantage of Base is the more mature USDC liquidity and wallet support, and only Base provides upto post-measurement.

Note: SKALE currently only supports exact. If preferScheme: 'upto' is passed under network: 'skale', the handler will silently fall back to exact if it cannot find upto, without throwing an error—scenarios like chat completions that are measured by token will thus settle at a fixed price instead of actual usage. Please use Base for post-measurement.

Browser Wallet Example

When using MetaMask, Coinbase Wallet, or WalletConnect in a frontend application, the EIP-1193 provider is usually passed directly:

import { AceDataCloud } from '@acedatacloud/sdk';
import { createX402PaymentHandler } from '@acedatacloud/x402-client';

const [address] = await window.ethereum.request({ method: 'eth_requestAccounts' });

const client = new AceDataCloud({
  paymentHandler: createX402PaymentHandler({
    network: 'base',
    evmProvider: window.ethereum,
    evmAddress: address
  })
});

const image = await client.images.generate({
  provider: 'nano-banana',
  prompt: 'a yellow banana on a white background'
});

The browser wallet will pop up a signature confirmation. The user is not signing any arbitrary message, but the payment request returned by the API: the receiving address, USDC contract, amount, validity period, and nonce are all included in the signature.

Solana Example

Solana uses SPL USDC TransferChecked. The incoming wallet adapter needs to expose publicKey and signAndSendTransaction.

import { AceDataCloud } from '@acedatacloud/sdk';
import { createX402PaymentHandler } from '@acedatacloud/x402-client';

const client = new AceDataCloud({
  paymentHandler: createX402PaymentHandler({
    network: 'solana',
    solanaWallet: phantomWallet
  })
});

const result = await client.openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Say hi in 3 words' }],
  max_tokens: 10
});

The Solana path currently only supports exact and does not support upto. If the API returns multiple accepts, the handler will choose the one with network = 'solana'.

The Solana path has been verified to return HTTP 200 and ADC_SOLANA_E2E_OK on the same public API for paid retries. Public RPC queries may be rate-limited, so this article does not write the Solana tx hash; for on-chain reconciliation, please use your own Solana RPC or console to record confirmations.

Choosing exact or upto

The current TypeScript handler will select the first matching payment requirement returned by the server for the network. YuJun's API typically places the exact for the same network before upto, so if you explicitly want to use post-measurement, you need to pass preferScheme: 'upto'.

Example:

const client = new AceDataCloud({
  paymentHandler: createX402PaymentHandler({
    network: 'base',
    evmProvider,
    evmAddress: wallet.address,
    preferScheme: 'upto'
  })
});

If the server does not return the upto requirement for that network, the handler will automatically fall back to the first available requirement for that network, which is usually exact.

upto requires a one-time authorization of Permit2. upto is currently only available on Base, so only one authorization for Base USDC is needed:

npx tsx scripts/approve-permit2.ts --network base

Base upto has completed public API verification: HTTP 402 -> HTTP 200, the subsequent settlement tx is 0x4b0b836ce1cd1171cdbc37df1637150b024214ec28e7f6f2d09122f15cbfc036. For complete output, see the billing plan description.

What the SDK does

The transport of @acedatacloud/sdk will execute a payment handler upon receiving a 402:

type PaymentHandler = (ctx: {
  url: string;
  method: string;
  body?: unknown;
  accepts: PaymentRequirement[];
}) => Promise<{ headers: Record<string, string> }>;

The handler returned by @acedatacloud/x402-client will:

  1. Select the payment requirement for the target network from ctx.accepts.
  2. Construct an EVM EIP-712 signature or Solana transfer transaction according to the network.
  3. Serialize the envelope to Base64.
  4. Return { headers: { 'PAYMENT-SIGNATURE': '<base64>' } }.
  5. The SDK will automatically retry with the original request body.

This means that business code only needs to be written like a normal SDK call, without needing to manually handle 402 retries.