Token Kiosk
Integrations

Frontend integration

Build a UI with wallet connect, x402 top-up, SIWE sign-in, and completions.

The gateway is a credit-based LLM inference API. Users fund a balance, authenticate with their wallet (SIWE), get an API key, and call the inference endpoint.

User flow

Connect wallet → Top up USDC → Sign in (SIWE) → Create API key → Use API key for completions

Top-up and sign-in are independent — both require a connected wallet but can happen in either order.

PurposeLibrary
Wallet connectionwagmi, RainbowKit, ConnectKit
SIWE message constructionsiwe
Viem wallet clientviem
x402 payment@x402/fetch or @x402/client
OpenAI-compatible clientopenai (point baseURL at the gateway)

Top up USDC balance

Top-up uses x402 — the client pays USDC on Base before the balance is credited.

import { withPaymentInterceptor } from '@x402/fetch'

const fetchWithPayment = withPaymentInterceptor(fetch, walletClient)

const res = await fetchWithPayment(`${BASE_URL}/v1/topup`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: 5 }),
})
const { balance_usdc, credited_usdc } = await res.json()
// $1 = 1_000_000 micro-USDC

See Top up (x402) for the underlying two-step protocol.

SIWE authentication

SIWE proves wallet ownership for API key management. There is no server-side session — every call needs a fresh signed message (valid 5 minutes).

import { SiweMessage } from 'siwe'
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'

const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
})
const [address] = await walletClient.getAddresses()

const siweMsg = new SiweMessage({
  domain: window.location.host,
  address,
  uri: window.location.origin,
  version: '1',
  chainId: 8453, // Base mainnet
  nonce: crypto.randomUUID().replace(/-/g, '').slice(0, 16),
  issuedAt: new Date().toISOString(),
  statement: 'Sign in to the AI Gateway',
})

const message = siweMsg.prepareMessage()
const signature = await walletClient.signMessage({ account: address, message })

Generate a fresh issuedAt timestamp immediately before each API call. The server rejects SIWE messages older than 5 minutes.

Chat completions in the browser

import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: `${BASE_URL}/v1`,
  apiKey: 'sk-a3f9...',
  dangerouslyAllowBrowser: true,
})

const response = await client.chat.completions.create({
  model: 'gemini/gemini-2.5-flash',
  messages: [{ role: 'user', content: 'Hello!' }],
})

Streaming (SSE)

const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ model, messages, max_tokens: 512, stream: true }),
})

const reader = res.body!.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  const chunk = decoder.decode(value)
  for (const line of chunk.split('\n')) {
    if (!line.startsWith('data: ')) continue
    const data = line.slice(6)
    if (data === '[DONE]') break
    const delta = JSON.parse(data).choices[0].delta.content ?? ''
    // append delta to your UI
  }
}

Units & conversions

ValueUnitDisplay
balance_usdcmicro-USDC (6 decimals)/ 1_000_000 → USD
amount in topupUSD (float)e.g. 5 = $5
promptPricePer1MTokensUSD per 1M tokens
total_charged_usdcmicro-USDC/ 1_000_000 → USD

On this page