// 1. Sign up with a wallet: get a challenge, sign it, verify (POST /v1/iam/web3/verify), exchange for a token.const nonce = await fetch(`${ID}/v1/iam/web3/nonce?chain=evm&address=${account.address}`).then((r) => r.json())const message = [`${nonce.data.domain} wants you to sign in with your Ethereum account:`, account.address, '', ...(nonce.data.statement ? [nonce.data.statement, ''] : []), `URI: ${nonce.data.uri}`, `Version: ${nonce.data.version}`, `Nonce: ${nonce.data.nonce}`, `Issued At: ${nonce.data.issuedAt}`].join('\n')const signature = await account.signMessage({ message })// Verify the signed challenge: a first-seen wallet gets an account and an organization of its own.const verifier = randomBytes(32).toString('base64url')const challenge = createHash('sha256').update(verifier).digest('base64url')const { data: code } = await fetch(`${ID}/v1/iam/web3/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientId: CLIENT, chain: 'evm', scheme: 'secp256k1-eip191', address: account.address, message, signature, type: 'code', redirectUri: 'http://localhost:3000/callback', scope: 'openid profile email', codeChallenge: challenge, codeChallengeMethod: 'S256' }),}).then((r) => r.json())const token = await fetch(`${ID}/v1/iam/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: CLIENT, code, code_verifier: verifier, redirect_uri: 'http://localhost:3000/callback' }),}).then((r) => r.json())
// 2. Get an API key (POST /v1/account/keys) that outlives the token. Shown once.const hanzo = new Hanzo({ apiKey: token.access_token, baseURL: API })const { key } = await hanzo.account.keys.create({ name: 'agent', type: 'secret' })
// 3. Use AI (POST /v1/chat/completions) with the Hanzo SDK — OpenAI-compatible, one line.const client = new Hanzo({ apiKey: key, baseURL: API })const chat = await client.chat.completions.create({ model: 'zen-free', messages: [{ role: 'user', content: 'Reply with the single word: ready' }],})console.log('chat', chat.choices[0]?.message?.content, 'free left today:', chat.headers?.get?.('x-ratelimit-remaining'))// Or make a Kai decision — typed, calibrated, zero output tokens.const decision = await client.decisions.create({ model: 'kai', state: { ticket: 'The export button throws a 500 error since this morning.' }, questions: { team: { type: 'choice', instructions: 'Which team should handle this ticket?', criteria: { billing: 'charges, invoices, refunds', tech: 'bugs, errors, outages', other: 'anything else' } } },})console.log('decision', decision.answers, 'input tokens:', decision.usage.input_tokens, 'latency:', decision.latency_ms + 'ms')
// 4. Top up in USDC or Card (POST /v1/billing/crypto/deposit · GET /v1/billing/crypto/options · GET /v1/billing/tier).const deposit = await client.billing.crypto.deposit({ chain: 'base', token: 'usdc', amountCents: 500 })console.log('send to', deposit.depositAddress, 'then poll', `${API}/v1/billing/crypto/deposit/${deposit.id}`)console.log('HANZO_API_KEY=' + key.slice(0, 12) + '… (store the whole key; it is shown once)')