<aside>

Overview

:ambient-logo-original: Ambient Documentation

What is Ambient?

Getting Started

Testnet

App

API

On-Chain

Tool Oracle

Claude Code

OpenClaw

OpenCode

Miners

Encyclopedia

Solana Quickstart

X402 Client Documentation

Headless x402 Subscriptions

Developers

OpenAI SDK

Anthropic SDK

API Docs

API Playground

Links

🌐 Homepage

✉️ [email protected]

</aside>

Overview

JumpGate supports pay-per-request AI inference through the x402 payment protocol. Clients send a chat request, receive a quote for that exact request, then use an x402 client to pay with USDC and stream the response.

Recommended endpoint: POST <https://jumpgate.ambient.xyz/paid/chat/v2>

Quote endpoint: POST <https://jumpgate.ambient.xyz/paid/chat/v2/quote>

New clients should use /paid/chat/v2. /paid/chat may remain available for older x402 v1 clients, but it is not the recommended integration path.

Legacy vault-backed relayer endpoints are no longer the supported integration path.


Supported Payment Networks

JumpGate accepts these trusted USDC rails:

Network CAIP-2 identifier Canonical USDC asset Trusted recipient
Solana mainnet solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v AYYo37bgztqAgP6wp2S9SiYWcijr8gz2o14c8RGWojJZ
Base mainnet eip155:8453 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 0x6992c688c56be442efe1e1ce7d5c95212ed7d59b

Validate network, asset, recipient, and amount before signing. Normalize Base addresses to lowercase before comparison; preserve case for Solana identifiers. Use Solana when you want lower network fees. Use Base when your application already has EVM wallet infrastructure.


Prerequisites

Before making a paid request, your client needs:


How x402 Works

The x402 protocol uses the HTTP 402 Payment Required response to negotiate payment for a normal HTTP request.

For JumpGate:

  1. Your client sends the paid chat request to JumpGate.
  2. JumpGate returns x402 payment requirements for Solana and Base.
  3. Your x402 client selects a supported network and signs the USDC payment.
  4. The client retries the request with payment proof.
  5. JumpGate verifies the payment and streams the inference response as SSE.

The official x402 client libraries handle the payment negotiation and retry. Your application still controls the request body, pricing quote, wallet selection, and local spend cap.


Request Flow

1. Build the inference request body.
2. POST the same body to /paid/chat/v2/quote, plus optional max_tool_calls.
3. Read quote.headers, amount fields, token counts, model_tier, and max spend fields.
4. POST the original inference request to /paid/chat/v2 with quote.headers copied unchanged.
5. Let the x402 client handle 402 -> sign/pay -> retry.
6. Read the final response as Server-Sent Events.

The quote endpoint uses the same chat tokenization and pricing validation as /paid/chat/v2, so clients should not estimate token counts locally.


Quote Request

Send the same inference body you will send to /paid/chat/v2. Add max_tool_calls when your client wants to reserve budget for tool calls.

{
  "model": "ambient/large",
  "messages": [
    {
      "role": "user",
      "content": "Explain verifiable inference in one paragraph."
    }
  ],
  "stream": true,
  "is_paid": true,
  "max_completion_tokens": 1000,
  "max_tool_calls": 0
}

Quote response:

{
  "headers": {
    "x402-input-tokens": "17",
    "x402-max-completion-tokens": "1000",
    "x402-model-tier": "standard",
    "x402-max-tool-calls": "0"
  },
  "input_tokens": 17,
  "output_tokens": 1000,
  "model_tier": "standard",
  "max_tool_calls": 0,
  "amount_micro_usdc": 4424,
  "amount_usdc": 0.004424,
  "max_amount_micro_usdc": 4474,
  "max_amount_usdc": 0.004474
}

Copy headers unchanged into the paid chat request. Use max_amount_micro_usdc as the local spend cap for the x402 payment requirement.


Pricing Headers

The quote response returns the pricing headers required by /paid/chat/v2:

Do not calculate these headers from byte length or character length. Request a quote and forward the returned values.

For human-readable pricing, see Ambient Billing.


TypeScript Client

Use Node.js 20.18 or newer and pin the payment and wallet libraries used by the integration:

npm install --save-exact @x402/[email protected] @x402/[email protected] @x402/[email protected] @x402/[email protected] [email protected] @solana/[email protected] @solana/[email protected]

Paid-chat only: The automatic wrapFetchWithPayment example below is for account-free /paid/chat/v2 calls. Never use this wrapper for a headless subscription payment_url; subscription clients must durably save one authorization and transmit it only once.

Shared request and quote helpers:

const JUMPGATE_URL = '<https://jumpgate.ambient.xyz>';
const PAID_CHAT_URL = `${JUMPGATE_URL}/paid/chat/v2`;
const QUOTE_URL = `${PAID_CHAT_URL}/quote`;

const request = {
  model: 'ambient/large',
  messages: [{ role: 'user', content: 'Hello from x402.' }],
  stream: true,
  is_paid: true,
  max_completion_tokens: 1000
};

async function quoteChat(request: unknown) {
  const response = await fetch(QUOTE_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...request, max_tool_calls: 0 })
  });

  if (!response.ok) throw new Error(await response.text());
  return await response.json();
}

Payment and streaming helper:

import { x402Client } from '@x402/core/client';
import { wrapFetchWithPayment } from '@x402/fetch';

const TRUSTED_RAILS: Record<string, { asset: string; payTo: string }> = {
  'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': {
    asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
    payTo: 'AYYo37bgztqAgP6wp2S9SiYWcijr8gz2o14c8RGWojJZ'
  },
  'eip155:8453': {
    asset: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
    payTo: '0x6992c688c56be442efe1e1ce7d5c95212ed7d59b'
  }
};

function paymentAmountMicroUsdc(requirement: { amount?: string; maxAmountRequired?: string }) {
  return BigInt(requirement.amount ?? requirement.maxAmountRequired ?? 0);
}

function isTrustedRequirement(requirement, maxAmount: bigint) {
  const rail = TRUSTED_RAILS[requirement.network];
  if (!rail || requirement.scheme !== 'exact') return false;
  const normalize = (value: string) =>
    requirement.network.startsWith('eip155:') ? value.toLowerCase() : value;
  const amount = paymentAmountMicroUsdc(requirement);
  return (
    normalize(requirement.asset) === normalize(rail.asset) &&
    normalize(requirement.payTo) === normalize(rail.payTo) &&
    amount > 0n &&
    amount <= maxAmount
  );
}

async function streamPaidChat({ quote, request, registerScheme }) {
  const client = new x402Client();
  const maxAmount = BigInt(quote.max_amount_micro_usdc ?? quote.amount_micro_usdc);

  client.registerPolicy((_version, requirements) =>
    requirements.filter((requirement) => isTrustedRequirement(requirement, maxAmount))
  );

  registerScheme(client);

  const fetchWithPayment = wrapFetchWithPayment(fetch, client);
  const response = await fetchWithPayment(PAID_CHAT_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'text/event-stream',
      ...quote.headers
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) throw new Error(await response.text());

  for await (const payload of readSse(response)) {
    console.log(payload);
  }
}

SSE parser:

async function* readSse(response: Response) {
  const reader = response.body?.getReader();
  if (!reader) throw new Error('Response body is not readable.');

  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const frames = buffer.split(/\r?\n\r?\n/);
    buffer = frames.pop() || '';

    for (const frame of frames) {
      const data = frame
        .split(/\r?\n/)
        .filter((line) => line.startsWith('data:'))
        .map((line) => line.slice(5).trimStart())
        .join('\n');

      if (data && data !== '[DONE]') yield JSON.parse(data);
    }
  }
}

Solana signer

Use a connected Solana wallet that can sign transactions.

import { getBase64EncodedWireTransaction, address as solanaAddress } from '@solana/kit';
import { VersionedTransaction } from '@solana/web3.js';
import { toClientSvmSigner } from '@x402/svm';
import { registerExactSvmScheme } from '@x402/svm/exact/client';

function createSolanaSigner(provider, publicKeyString: string) {
  return toClientSvmSigner({
    address: solanaAddress(publicKeyString),
    signTransactions: async (transactions) =>
      await Promise.all(
        transactions.map(async (transaction) => {
          const encoded = getBase64EncodedWireTransaction(transaction);
          const web3Transaction = VersionedTransaction.deserialize(base64ToBytes(encoded));
          const signed = await provider.signTransaction(web3Transaction);
          const signerIndex = signed.message.staticAccountKeys.findIndex(
            (key) => key.toBase58() === publicKeyString
          );

          return Object.freeze({ [publicKeyString]: signed.signatures[signerIndex] });
        })
      )
  });
}

const solanaSigner = createSolanaSigner(window.solana, window.solana.publicKey.toBase58());
const quote = await quoteChat(request);
await streamPaidChat({
  quote,
  request,
  registerScheme: (client) => registerExactSvmScheme(client, { signer: solanaSigner })
});

Base signer

Use a Base-compatible EVM wallet. Switch to Base before signing.

import { createPublicClient, createWalletClient, custom, http } from 'viem';
import { base } from 'viem/chains';
import { toClientEvmSigner } from '@x402/evm';
import { registerExactEvmScheme } from '@x402/evm/exact/client';

async function createBaseSigner(provider) {
  await provider.request({
    method: 'wallet_switchEthereumChain',
    params: [{ chainId: '0x2105' }]
  });

  const walletClient = createWalletClient({ chain: base, transport: custom(provider) });
  const [account] = await walletClient.requestAddresses();
  const publicClient = createPublicClient({ chain: base, transport: http('<https://mainnet.base.org>') });

  return toClientEvmSigner(
    {
      address: account,
      signTypedData: (message) => walletClient.signTypedData({ account, ...message })
    },
    publicClient
  );
}

const baseSigner = await createBaseSigner(window.ethereum);
const quote = await quoteChat(request);
await streamPaidChat({
  quote,
  request,
  registerScheme: (client) =>
    registerExactEvmScheme(client, {
      signer: baseSigner,
      networks: ['eip155:8453'],
      schemeOptions: { 8453: { rpcUrl: '<https://mainnet.base.org>' } }
    })
});

Response Format

Successful paid chat responses stream Server-Sent Events:

data: {"Content":{"choices":[{"delta":{"content":"Hello"}}]}}

data: {"Usage":{"usage":{"prompt_tokens":5,"completion_tokens":10,"total_tokens":15}}}

data: {"Verification":{"verified":true}}

data: [DONE]

Handle at least these event types:


Testing & Debugging

Related x402 flow