# Table of Contents - [Based AI | AI inference, billed in USDC](#based-ai-ai-inference-billed-in-usdc) - [Based AI | AI inference, billed in USDC](#based-ai-ai-inference-billed-in-usdc) --- # Based AI | AI inference, billed in USDC Quick Start ----------- Get up and running with Based AI in under a minute. The gateway is fully OpenAI-compatible — any SDK or HTTP client that works with OpenAI will work here. 1 Create an account [Sign up](https://ai.based.one/login) with email, Google, or GitHub. You'll receive $2 in free USDC credits. 2 Create an API token Go to the [API Tokens](https://ai.based.one/api-tokens) page in the dashboard and create a new token. Copy the `sk-...` value — it is only shown once. 3 Make your first request Use any OpenAI-compatible client. Set the base URL to `https://ai.based.one/v1` and use your API token. bash curl https://ai.based.one/v1/chat/completions \ -H "Authorization: Bearer sk-your-api-token" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4.1-mini", "messages": [{"role": "user", "content": "Hello!"}] }' Authentication -------------- Based AI supports two authentication methods. All authenticated endpoints expect an `Authorization: Bearer ` header. #### API Tokens (recommended) Create persistent API tokens from the dashboard. Tokens are prefixed with `sk-` and can be used in any HTTP client or SDK. They are hashed with SHA-256 before storage — the plaintext is only shown once at creation time. Tokens can have an optional expiry and can be revoked at any time. #### Privy Identity Tokens If you're building a frontend that uses Privy for authentication, you can use the Privy identity token directly as the Bearer token. The gateway verifies it against Privy's ES256 public key. #### x402 (no auth required) For autonomous agents, you can skip authentication entirely and use the [x402 payment protocol](https://ai.based.one/docs#x402) to pay per-request with USDC or USDH on Hyperliquid. API Reference ------------- Base URL: `https://ai.based.one`. All request and response bodies are JSON. ### Account GET `/v1/me` Returns account info, wallet balance, billing settings, usage stats, and recent deposits. RESPONSE { "accountId": "acc_...", "email": "user@example.com", "paymentToken": "USDC", "privyWalletConfigured": true, "walletAddress": "0x264F84E7...", "wallet": { "freeCreditsUsdc": 1.9999, "paidCreditsUsdc": 15.5, "perpUsdc": 5.0, "spotUsdc": 0.0, "spotUsdh": 0.0, "currency": "USDC", "chain": "hyperliquid" }, "dailyUsage": { "spendUsdc": 0.5, "capUsdc": 10, "remainingUsdc": 9.5 }, "weeklyUsage": { "spendUsdc": 3.2, "capUsdc": 50, "remainingUsdc": 46.8 }, "stats": { "totalRequests": 142, "totalSpendUsdc": 3.75, "totalTokens": 52400, "requestsToday": 12, "requestsThisWeek": 89, "activeApiTokens": 2, "avgCostPerRequest": 0.0264 }, "recentDeposits": [], "recentUsage": [] } PUT `/v1/me/billing` Update your preferred deposit token (USDC or USDH). REQUEST BODY { "paymentToken": "USDC" | "USDH" } All inference is billed against your internal credit balance. This setting controls which token is used when depositing from your on-chain wallet to credits. ### API Tokens POST `/v1/api-tokens` Create a new API token for programmatic access. REQUEST BODY { "name": "my-production-key", "expiresInDays": 90 // optional } RESPONSE (201) { "id": "tok_...", "name": "my-production-key", "token": "sk-abc123...", "expiresAtMs": 1735689600000, "note": "Store this token — it won't be shown again." } GET `/v1/api-tokens` List all API tokens for the authenticated account. DELETE `/v1/api-tokens/:id` Revoke an API token. It can no longer be used for authentication. ### Models GET `/v1/models` List all available models with pricing. Filter by track with ?track=standard or ?track=privacy. RESPONSE { "object": "list", "data": [\ {\ "id": "openai/gpt-4.1-mini",\ "provider": "OpenAI",\ "name": "GPT-4.1 Mini",\ "track": "standard",\ "pricing": {\ "inputPerMillionTokens": 0.42,\ "outputPerMillionTokens": 1.68,\ "currency": "USDC"\ },\ "capabilities": ["chat", "vision"],\ "description": null\ }\ ] } ### Wallet & Deposits POST `/v1/wallet/deposit` Transfer USDC from your on-chain wallet to your internal credit balance. REQUEST BODY { "amount": 10.0 } Charges your Privy wallet on Hyperliquid Core L1 and adds the amount to your credit balance. GET `/v1/wallet/deposits` List deposit history for the authenticated account. POST `/v1/wallet/send-asset` Send USDC or USDH from your embedded wallet to another Hyperliquid Core L1 address. REQUEST BODY { "destination": "0x...", "token": "USDC", "amount": 5.0 } Transfers are instant and gas-free on Hyperliquid Core L1.`token` must be `"USDC"` or `"USDH"`. Cannot send to your own wallet or the treasury address. ### LLM API (OpenAI-Compatible) All API routes are OpenAI-compatible. Use any SDK that supports a custom base URL. Models are referenced with a `provider/model` format. POST `/v1/chat/completions` Chat completions with streaming support. Supports all OpenAI-compatible parameters. REQUEST BODY { "model": "openai/gpt-4.1-mini", "messages": [\ { "role": "user", "content": "Hello!" }\ ], "stream": true, "max_tokens": 512 } For privacy models, use the `privacy/` prefix: `privacy/kimi-k2.5` POST `/v1/embeddings` Generate embeddings for text input. REQUEST BODY { "model": "mistral/mistral-embed", "input": "Some text to embed." } POST `/v1/images/generations` Generate images from text prompts. REQUEST BODY { "model": "openai/dall-e-3", "prompt": "A cat wearing sunglasses on a beach." } GET `/healthz` Health check. Returns 200 if the gateway is running. Models & Pricing ---------------- Models are organized into two tracks. All pricing is per-million tokens in USDC, charged after each request based on actual usage. #### ![](https://ai.based.one/based-icon-black-bg.png) Standard Track Routed through AI providers. Same pricing as upstream providers. | Model | Input / 1M tokens | Output / 1M tokens | | --- | --- | --- | | openai/gpt-5-mini | $0.26 | $2.10 | | openai/gpt-4.1-mini | $0.42 | $1.68 | | anthropic/claude-sonnet-4.6 | $3.15 | $15.75 | | deepseek/deepseek-v3.2 | $0.27 | $0.40 | | google/gemini-2.5-flash | $0.32 | $2.63 | | mistral/mistral-small | $0.11 | $0.32 | #### Privacy Track Self-hosted GPU infrastructure running open-weight models. Zero logging, zero data retention. 1.5x markup over standard. | Model | Input / 1M tokens | Output / 1M tokens | | --- | --- | --- | | privacy/minimax-m2.5 | $0.45 | $1.80 | | privacy/kimi-k2.5 | $0.75 | $4.20 | Use `GET /v1/models` to see the full, up-to-date list of models and their pricing programmatically. Billing ------- All inference is billed against your internal **credit balance**. Your account is checked for a minimum credit balance before each request, and the actual cost (based on real token counts) is debited after inference completes. #### Post-Charge Flow 1. 1.User sends an inference request 2. 2.Gateway checks minimum credit balance ($0.50 USDC) 3. 3.Request is forwarded to the upstream provider 4. 4.Actual token usage is measured from the response 5. 5.Cost is debited from your credit balance (free credits consumed first, then paid credits) #### Credits Every new account starts with `$2.00 USDC` in free credits. You can add more by transferring USDC from your on-chain Hyperliquid Core L1 wallet to your credit balance via the Billing page. Your embedded Privy wallet is created automatically on sign-up. Send USDC to your wallet address on Hyperliquid Core L1, then use the dashboard to transfer it to credits. x402 Payments ------------- All LLM proxy routes support the [x402 protocol](https://www.x402.org/) for autonomous, account-free payments with USDC or USDH on Hyperliquid. [View full integration guide](https://ai.based.one/docs/x402) #### Protocol Flow 1. 1.Agent sends request without `Authorization` header 2. 2.Gateway returns `402` with Hyperliquid payment terms (USDC + USDH options) 3. 3.Agent constructs a `sendAsset` action and signs it via EIP-712 4. 4.Agent retries with `X-PAYMENT` header (base64 of signed action) 5. 5.Gateway recovers signer, submits to Hyperliquid, returns completion + `X-Session-Token` #### Ceiling Formula ceiling = (estimatedInputTokens × 1.15 × inputRate) + (maxOutputTokens × outputRate) `estimatedInputTokens` — ~4 chars/token heuristic + 15% buffer `maxOutputTokens` — `max_tokens` from request, or default 4096 Tip: Set `max_tokens` to minimize the ceiling price. #### Example Ceilings | Model | Input | max\_tokens | Ceiling | | --- | --- | --- | --- | | gpt-4.1-mini | 1K | 512 | ~$0.001 | | gpt-4.1-mini | 1K | 4,096 | ~$0.007 | | claude-sonnet-4.6 | 5K | 2,048 | ~$0.046 | | deepseek-v3.2 | 1K | 1,024 | ~$0.001 | #### Response Headers | Header | Description | | --- | --- | | X-Payment-Ceiling | Ceiling price charged (USDC) | | X-Payment-Actual | Actual cost consumed (USDC) | | X-Credits-Remaining | Credit balance after this request (USDC) | | X-Session-Token | Bearer token for subsequent requests (issued on every payment) | #### Hyperliquid Rate Limits Hyperliquid enforces rate limits on actions like `sendAsset`. Your agent code should account for these: * **IP limit:** 1,200 weighted requests per minute across all REST calls. Each `sendAsset` (exchange action) costs weight **1**. * **Address limit:** New wallets start with a buffer of 10,000 requests. Once exhausted, you are throttled to **1 request every 10 seconds**. The buffer replenishes based on cumulative USDC volume traded. * **Practical impact:** An agent sending rapid x402 payments from the same wallet can hit the per-address limit. Once throttled, Hyperliquid rejects the action until the cooldown elapses. Recommended pattern * On your **first request**, pay via `X-PAYMENT` to receive a `X-Session-Token`. * Use the session token as a `Bearer` token for all subsequent requests — this consumes from your credit balance with **zero on-chain calls**. * Only send another `X-PAYMENT` when your credits run out (indicated by a new `402` response). * Set `max_tokens` to keep ceilings low — this means more requests per top-up. #### Full Example Complete TypeScript example showing the x402 flow end-to-end: request → receive 402 → sign → retry → use session token. import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; const API = "https://ai.based.one"; const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"; // EIP-712 domain for Hyperliquid sendAsset const HL_DOMAIN = { name: "HyperliquidSignTransaction", version: "1", chainId: 421614, verifyingContract: "0x0000000000000000000000000000000000000000", }; const SEND_ASSET_TYPES = { "HyperliquidTransaction:SendAsset": [\ { name: "hyperliquidChain", type: "string" },\ { name: "destination", type: "string" },\ { name: "sourceDex", type: "string" },\ { name: "destinationDex", type: "string" },\ { name: "token", type: "string" },\ { name: "amount", type: "string" },\ { name: "fromSubAccount", type: "string" },\ { name: "nonce", type: "uint64" },\ ], }; // ── Step 1: Send request without auth ────────────────────────── const body = { model: "openai/gpt-4.1-mini", messages: [{ role: "user", content: "Hello from an agent!" }], max_tokens: 256, }; let res = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (res.status !== 402) throw new Error("Expected 402"); // ── Step 2: Parse payment terms ──────────────────────────────── const terms = await res.json(); const accept = terms.accepts[0]; // pick USDC (index 0) or USDH (1) const amount = (parseInt(accept.maxAmountRequired) / 1e6).toString(); const nonce = Date.now(); // ── Step 3: Sign the sendAsset action (EIP-712) ──────────────── const account = privateKeyToAccount(PRIVATE_KEY); const walletClient = createWalletClient({ account, transport: http(), }); const message = { hyperliquidChain: accept.extra.hyperliquidChain, destination: accept.payTo, sourceDex: accept.extra.sourceDex, destinationDex: accept.extra.destinationDex, token: accept.asset, amount, fromSubAccount: "", nonce: BigInt(nonce), }; const signature = await walletClient.signTypedData({ domain: HL_DOMAIN, types: SEND_ASSET_TYPES, primaryType: "HyperliquidTransaction:SendAsset", message, }); // Split signature into { r, s, v } const r = "0x" + signature.slice(2, 66); const s = "0x" + signature.slice(66, 130); const v = parseInt(signature.slice(130, 132), 16); // ── Step 4: Build X-PAYMENT header ───────────────────────────── const payment = { action: { type: "sendAsset", hyperliquidChain: accept.extra.hyperliquidChain, signatureChainId: accept.extra.signatureChainId, destination: accept.payTo, sourceDex: accept.extra.sourceDex, destinationDex: accept.extra.destinationDex, token: accept.asset, amount, fromSubAccount: "", nonce, }, nonce, signature: { r, s, v }, }; const xPayment = btoa(JSON.stringify(payment)); // ── Step 5: Retry with payment ───────────────────────────────── res = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "X-PAYMENT": xPayment, }, body: JSON.stringify(body), }); const data = await res.json(); console.log(data.choices[0].message.content); // ── Step 6: Use session token for subsequent requests ────────── const sessionToken = res.headers.get("X-Session-Token"); if (sessionToken) { // All future requests use Bearer auth (no more 402s until credit runs out) const next = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${sessionToken}`, }, body: JSON.stringify({ model: "openai/gpt-4.1-mini", messages: [{ role: "user", content: "Follow-up question" }], max_tokens: 256, }), }); console.log(await next.json()); } Any wallet that supports EIP-712 signing works. The session token persists as long as there are credits remaining from ceiling overpayment. Rate Limits ----------- Based AI enforces both spend-based and request-based rate limits to protect the network and your account. | Limit | Default | Description | | --- | --- | --- | | Daily spend cap | $10 USDC | Maximum spend per 24-hour rolling window | | Weekly spend cap | $50 USDC | Maximum spend per 7-day rolling window | | Minimum balance | $0.50 USDC | Required balance to initiate a request | | Abuse protection | Automatic | Durable Object-based rate limiting per account | Code Examples ------------- #### Chat Completions curlPythonTypeScript curl https://ai.based.one/v1/chat/completions \ -H "Authorization: Bearer sk-your-api-token" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4.1-mini", "messages": [\ {"role": "system", "content": "You are a helpful assistant."},\ {"role": "user", "content": "What is quantum computing?"}\ ], "max_tokens": 512 }' #### Streaming PythonTypeScript from openai import OpenAI client = OpenAI( base_url="https://ai.based.one/v1", api_key="sk-your-api-token", ) stream = client.chat.completions.create( model="openai/gpt-4.1-mini", messages=[{"role": "user", "content": "Write a haiku about code."}], stream=True, ) for chunk in stream: content = chunk.choices[0].delta.content or "" print(content, end="", flush=True) #### Embeddings Pythoncurl from openai import OpenAI client = OpenAI( base_url="https://ai.based.one/v1", api_key="sk-your-api-token", ) response = client.embeddings.create( model="mistral/mistral-embed", input="The quick brown fox jumps over the lazy dog.", ) print(f"Embedding dimension: {len(response.data[0].embedding)}") #### x402 Payment (Agent — Minimal) TypeScript // Minimal x402 helper — wraps any fetch with automatic 402 handling async function x402Fetch(url: string, init: RequestInit, walletClient: any) { let res = await fetch(url, init); if (res.status !== 402) return res; const terms = await res.json(); const accept = terms.accepts[0]; // USDC const amount = (parseInt(accept.maxAmountRequired) / 1e6).toString(); const nonce = Date.now(); const sig = await walletClient.signTypedData({ domain: { name: "HyperliquidSignTransaction", version: "1", chainId: 421614, verifyingContract: "0x0000000000000000000000000000000000000000", }, types: { "HyperliquidTransaction:SendAsset": [\ { name: "hyperliquidChain", type: "string" },\ { name: "destination", type: "string" },\ { name: "sourceDex", type: "string" },\ { name: "destinationDex", type: "string" },\ { name: "token", type: "string" },\ { name: "amount", type: "string" },\ { name: "fromSubAccount", type: "string" },\ { name: "nonce", type: "uint64" },\ ], }, primaryType: "HyperliquidTransaction:SendAsset", message: { hyperliquidChain: accept.extra.hyperliquidChain, destination: accept.payTo, sourceDex: accept.extra.sourceDex, destinationDex: accept.extra.destinationDex, token: accept.asset, amount, fromSubAccount: "", nonce: BigInt(nonce), }, }); const r = "0x" + sig.slice(2, 66); const s = "0x" + sig.slice(66, 130); const v = parseInt(sig.slice(130, 132), 16); const payment = btoa(JSON.stringify({ action: { type: "sendAsset", ...accept.extra, destination: accept.payTo, token: accept.asset, amount, fromSubAccount: "", nonce, }, nonce, signature: { r, s, v }, })); return fetch(url, { ...init, headers: { ...Object.fromEntries(new Headers(init.headers)), "X-PAYMENT": payment }, }); } // Usage const res = await x402Fetch( "https://ai.based.one/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "openai/gpt-4.1-mini", messages: [{ role: "user", content: "Hello from an agent!" }], max_tokens: 256, }), }, walletClient // viem WalletClient ); const data = await res.json(); const sessionToken = res.headers.get("X-Session-Token"); [Back to home](https://ai.based.one/) [Get started](https://ai.based.one/login) --- # Based AI | AI inference, billed in USDC x402 Integration Guide ---------------------- The [x402 protocol](https://www.x402.org/) lets AI agents pay for inference per-request with USDC or USDH on Hyperliquid — no signup, no API key, no deposit flow required. #### How It Works Agent Gateway Hyperliquid │ │ │ │ POST /v1/chat/completions │ │ │ (no auth) │ │ │──────────────────────────────>│ │ │ │ │ │ 402 + payment terms │ │ │ (ceiling price, payTo, asset)│ │ │<──────────────────────────────│ │ │ │ │ │ Sign sendAsset (EIP-712) │ │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ > │ │ │ │ │ │ Retry + X-PAYMENT header │ │ │──────────────────────────────>│ Submit sendAsset │ │ │─────────────────────────> │ │ │ Transfer confirmed │ │ │<───────────────────────── │ │ │ │ │ 200 + completion │ │ │ + X-Session-Token │ │ │<──────────────────────────────│ │ │ │ │ │ Follow-up with Bearer token │ │ │ (uses carry-forward credits) │ │ │──────────────────────────────>│ │ After the first payment, the gateway returns an `X-Session-Token`. Use it as a Bearer token for follow-up requests — credits from overpayment carry forward. When credits run out, the gateway returns `402` again. Prerequisites ------------- * 1.A wallet with USDC (or USDH) balance on [Hyperliquid](https://app.hyperliquid.xyz/) * 2.`viem` for EIP-712 signing (or any library that supports `eth_signTypedData_v4`) * 3.Node.js / Bun / Deno with top-level await support bash npm install viem Step 1: Get Payment Terms ------------------------- Send an unauthenticated request. The gateway returns `402` with payment terms including the ceiling price, treasury address, and asset details. typescript const API = "https://ai.based.one"; const body = { model: "openai/gpt-4.1-mini", messages: [{ role: "user", content: "Hello from an agent!" }], max_tokens: 256, // keep this low to minimize ceiling price }; const res = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (res.status !== 402) throw new Error(`Expected 402, got ${res.status}`); const terms = await res.json(); console.log(terms); #### 402 Response Format json { "x402Version": 1, "accepts": [\ {\ "scheme": "exact",\ "network": "hyperliquid",\ "maxAmountRequired": "7000",\ "resource": "/v1/chat/completions",\ "description": "AI inference (openai/gpt-4.1-mini) — ceiling price $0.007000 USDC",\ "payTo": "0xbfD672732832dbaa2B4d712efd95E593ea30aACf",\ "asset": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",\ "maxTimeoutSeconds": 300,\ "extra": {\ "hyperliquidChain": "Mainnet",\ "signatureChainId": "0x66eee",\ "sourceDex": "",\ "destinationDex": ""\ }\ },\ {\ "scheme": "exact",\ "network": "hyperliquid",\ "maxAmountRequired": "7000",\ "asset": "USDH:0x54e00a5988577cb0b0c9ab0cb6ef7f4b",\ "extra": { "sourceDex": "spot", "destinationDex": "spot" }\ }\ ], "error": "X402_PAYMENT_REQUIRED", "message": "Payment of $0.007000 USDC (or equivalent USDH) required..." } `maxAmountRequired` is in the smallest unit (6 decimal places), so `"7000"` = $0.007. `accepts[0]` is USDC, `accepts[1]` is USDH. Pick whichever token you hold. Step 2: Sign the Payment ------------------------ Construct a Hyperliquid `sendAsset` action using the parameters from the 402 response, then sign it via EIP-712. #### EIP-712 Domain & Types typescript // EIP-712 domain for Hyperliquid sendAsset const HL_DOMAIN = { name: "HyperliquidSignTransaction", version: "1", chainId: 421614, // from parseInt(accept.extra.signatureChainId, 16) verifyingContract: "0x0000000000000000000000000000000000000000", }; const SEND_ASSET_TYPES = { "HyperliquidTransaction:SendAsset": [\ { name: "hyperliquidChain", type: "string" },\ { name: "destination", type: "string" },\ { name: "sourceDex", type: "string" },\ { name: "destinationDex", type: "string" },\ { name: "token", type: "string" },\ { name: "amount", type: "string" },\ { name: "fromSubAccount", type: "string" },\ { name: "nonce", type: "uint64" },\ ], }; #### Sign with viem typescript import { privateKeyToAccount } from "viem/accounts"; const accept = terms.accepts[0]; // USDC option const amount = (parseInt(accept.maxAmountRequired) / 1e6).toString(); const nonce = Date.now(); const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY"); const message = { hyperliquidChain: accept.extra.hyperliquidChain, destination: accept.payTo, sourceDex: accept.extra.sourceDex, destinationDex: accept.extra.destinationDex, token: accept.asset, amount, fromSubAccount: "", nonce: BigInt(nonce), }; const signature = await account.signTypedData({ domain: HL_DOMAIN, types: SEND_ASSET_TYPES, primaryType: "HyperliquidTransaction:SendAsset", message, }); // Split signature into { r, s, v } const r = "0x" + signature.slice(2, 66); const s = "0x" + signature.slice(66, 130); const v = parseInt(signature.slice(130, 132), 16); The `nonce` must be within 5 minutes of the current time to prevent replay attacks. Step 3: Submit Payment ---------------------- Base64-encode the signed payload and retry the same request with the `X-PAYMENT` header. typescript const payment = { action: { type: "sendAsset", hyperliquidChain: accept.extra.hyperliquidChain, signatureChainId: accept.extra.signatureChainId, destination: accept.payTo, sourceDex: accept.extra.sourceDex, destinationDex: accept.extra.destinationDex, token: accept.asset, amount, fromSubAccount: "", nonce, }, nonce, signature: { r, s, v }, }; const xPayment = btoa(JSON.stringify(payment)); const res2 = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "X-PAYMENT": xPayment, }, body: JSON.stringify(body), }); console.log("Status:", res2.status); // 200 #### Response Headers | Header | Description | | --- | --- | | X-Payment-Ceiling | Ceiling price charged (USDC) | | X-Payment-Actual | Actual cost consumed (USDC) | | X-Credits-Remaining | Credit balance after this request | | X-Session-Token | Bearer token for subsequent requests | typescript // Extract session token and cost info const sessionToken = res2.headers.get("X-Session-Token"); const ceiling = res2.headers.get("X-Payment-Ceiling"); const actual = res2.headers.get("X-Payment-Actual"); const remaining = res2.headers.get("X-Credits-Remaining"); console.log(`Ceiling: $${ceiling}, Actual: $${actual}, Remaining: $${remaining}`); // Parse the completion const completion = await res2.json(); console.log(completion.choices[0].message.content); Step 4: Use Session Token ------------------------- The difference between the ceiling price and actual cost stays as credit. Use the `X-Session-Token` as a Bearer token for follow-up requests — no on-chain payment needed. typescript // Follow-up request using session token (no payment) const res3 = await fetch(API + "/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${sessionToken}`, }, body: JSON.stringify({ model: "openai/gpt-4.1-mini", messages: [{ role: "user", content: "What is 2+2?" }], max_tokens: 16, }), }); console.log("Status:", res3.status); // 200 (paid from credits) const followUp = await res3.json(); console.log(followUp.choices[0].message.content); When credits are exhausted, the gateway returns `402` again. Repeat from Step 1 to top up. Full Working Script ------------------- Complete end-to-end script. Save as `test-x402.ts` and run with `npx tsx test-x402.ts`. typescript /** * x402 agentic payment flow — Based AI Gateway * * Usage: * TEST_PRIVATE_KEY=0x... npx tsx test-x402.ts * * Requires: npm install viem */ import { privateKeyToAccount } from "viem/accounts"; // ── Config ────────────────────────────────────────────────────── const GATEWAY_URL = process.env.GATEWAY_URL ?? "https://ai.based.one"; const MODEL = process.env.MODEL ?? "openai/gpt-4.1-mini"; const PRIVATE_KEY = process.env.TEST_PRIVATE_KEY; if (!PRIVATE_KEY) { console.error("Set TEST_PRIVATE_KEY env var (hex key with USDC on Hyperliquid)"); process.exit(1); } const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`); console.log(`Wallet: ${account.address}`); // ── EIP-712 constants ─────────────────────────────────────────── const HL_DOMAIN = { name: "HyperliquidSignTransaction" as const, version: "1" as const, chainId: 421614, verifyingContract: "0x0000000000000000000000000000000000000000" as `0x${string}`, }; const SEND_ASSET_TYPES = { "HyperliquidTransaction:SendAsset": [\ { name: "hyperliquidChain", type: "string" },\ { name: "destination", type: "string" },\ { name: "sourceDex", type: "string" },\ { name: "destinationDex", type: "string" },\ { name: "token", type: "string" },\ { name: "amount", type: "string" },\ { name: "fromSubAccount", type: "string" },\ { name: "nonce", type: "uint64" },\ ], } as const; // ── Step 1: Request without auth → 402 ───────────────────────── console.log("\n=== Step 1: Request without auth ==="); const chatBody = { model: MODEL, messages: [{ role: "user", content: "Say hello in one sentence." }], max_tokens: 128, }; const step1 = await fetch(`${GATEWAY_URL}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(chatBody), }); if (step1.status !== 402) { console.error(`Expected 402, got ${step1.status}`); process.exit(1); } const terms = await step1.json() as { accepts: Array<{ maxAmountRequired: string; payTo: string; asset: string; extra: { hyperliquidChain: string; signatureChainId: string; sourceDex: string; destinationDex: string; }; }>; }; const option = terms.accepts[0]; // USDC const ceilingUsdc = parseInt(option.maxAmountRequired) / 1_000_000; console.log(`Ceiling: $${ceilingUsdc.toFixed(6)}`); // ── Step 2: Sign sendAsset (overpay $1 for session credits) ──── console.log("\n=== Step 2: Sign sendAsset ==="); const payAmount = Math.max(ceilingUsdc, 1.0); // overpay for credits const nonce = Date.now(); const amount = payAmount.toFixed(6); const message = { hyperliquidChain: option.extra.hyperliquidChain, destination: option.payTo, sourceDex: option.extra.sourceDex, destinationDex: option.extra.destinationDex, token: option.asset, amount, fromSubAccount: "", nonce: BigInt(nonce), }; const sig = await account.signTypedData({ domain: HL_DOMAIN, types: SEND_ASSET_TYPES, primaryType: "HyperliquidTransaction:SendAsset", message, }); const sigBytes = sig.slice(2); const r = `0x${sigBytes.slice(0, 64)}`; const s = `0x${sigBytes.slice(64, 128)}`; const v = parseInt(sigBytes.slice(128, 130), 16); console.log(`Paying: $${amount} (ceiling: $${ceilingUsdc.toFixed(6)})`); // ── Step 3: Retry with X-PAYMENT header ──────────────────────── console.log("\n=== Step 3: Submit payment ==="); const xPayment = btoa(JSON.stringify({ action: { type: "sendAsset", hyperliquidChain: option.extra.hyperliquidChain, signatureChainId: option.extra.signatureChainId, destination: option.payTo, sourceDex: option.extra.sourceDex, destinationDex: option.extra.destinationDex, token: option.asset, amount, fromSubAccount: "", nonce, }, nonce, signature: { r, s, v }, })); const step3 = await fetch(`${GATEWAY_URL}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", "X-PAYMENT": xPayment, }, body: JSON.stringify(chatBody), }); console.log(`Status: ${step3.status}`); const sessionToken = step3.headers.get("X-Session-Token"); console.log(`Ceiling: $${step3.headers.get("X-Payment-Ceiling")}`); console.log(`Actual: $${step3.headers.get("X-Payment-Actual")}`); console.log(`Credits: $${step3.headers.get("X-Credits-Remaining")}`); if (!step3.ok) { console.error(await step3.text()); process.exit(1); } const completion = await step3.json() as any; console.log(`Response: ${completion.choices?.[0]?.message?.content}`); // ── Step 4: Follow-up with session token ─────────────────────── if (sessionToken) { console.log("\n=== Step 4: Follow-up with session token ==="); const step4 = await fetch(`${GATEWAY_URL}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${sessionToken}`, }, body: JSON.stringify({ model: MODEL, messages: [{ role: "user", content: "What is 2+2? One word." }], max_tokens: 16, }), }); console.log(`Status: ${step4.status}`); if (step4.ok) { const followUp = await step4.json() as any; console.log(`Response: ${followUp.choices?.[0]?.message?.content}`); } } console.log("\n=== Done ==="); Tips ---- #### Minimize ceiling price with max\_tokens The ceiling formula is `(inputTokens x 1.15 x inputRate) + (max_tokens x outputRate)`. If you only need a short answer, set `max_tokens: 128` instead of the default 4096. This can reduce the ceiling by 30x. #### Overpay for session credits Pay more than the ceiling to build up a credit balance. For example, pay $1 on the first request. Subsequent requests use the session token with zero on-chain calls until credits run out. This avoids Hyperliquid rate limits. #### Hyperliquid rate limits Hyperliquid enforces rate limits on `sendAsset` actions: * **IP limit:** 1,200 weighted requests/min * **Address limit:** New wallets get a 10,000-request buffer. Once exhausted, throttled to 1 request per 10 seconds. Use the session token pattern (pay once, use credits) to minimize on-chain calls. #### Supported routes | Route | Ceiling | | --- | --- | | POST /v1/chat/completions | Input estimate + max\_tokens output | | POST /v1/embeddings | Input estimate only | | POST /v1/images/generations | Flat rate (~$0.04) | ---