Frontend integration
Build a UI with wallet connect, USDT 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 USDT → Sign in (SIWE) → Create API key → Use API key for completionsTop-up and sign-in are independent — both require a connected wallet but can happen in either order.
Recommended libraries
| Purpose | Library |
|---|---|
| Wallet connection | wagmi, RainbowKit, ConnectKit |
| SIWE message construction | siwe |
| Viem wallet client | viem |
| OpenAI-compatible client | openai (point baseURL at the gateway) |
Top up USDT balance
Top-up is a plain USDT transfer on Ethereum to the collection wallet. The indexer credits the sending address, so send from the wallet the user signs in with. See Supported networks for the chain, token and rate.
import { erc20Abi } from 'viem'
const USDT_ETHEREUM = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
// Never hardcode the destination — it differs per deployment.
const { gatewayWalletAddress } = await (await fetch(`${BASE_URL}/v1/config`)).json()
await walletClient.writeContract({
address: USDT_ETHEREUM,
abi: erc20Abi,
functionName: 'transfer',
args: [gatewayWalletAddress, 5_000_000n], // 5 USDT, 6 decimals
})
// Poll the balance until the credit lands.
const { balance_usdc } = await (await fetch(`${BASE_URL}/v1/balance/${address}`)).json()
// $1 = 1_000_000 micro-USDIf a transfer is not credited, submit its hash to
/v1/topup/direct.
SIWE authentication
SIWE proves wallet ownership for API key management. There is no server-side session — every call needs a fresh signed message (valid 1 hour).
import { SiweMessage } from 'siwe'
import { createWalletClient, custom } from 'viem'
import { base } from 'viem/chains'
const walletClient = createWalletClient({
chain: base,
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, // not verified by the gateway — any chain ID works
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 1 hour.
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-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
| Value | Unit | Display |
|---|---|---|
balance_usdc | micro-USD (6 decimals) | / 1_000_000 → USD |
amount in topup | USD (float) | e.g. 5 = $5 |
promptPricePer1MTokens | USD per 1M tokens | — |
total_charged_usdc | micro-USD | / 1_000_000 → USD |