// Live check of quote escrow on inference.bsvkey.com, with your own channel. // // 1. Quote a job whose ceiling uses almost all of the channel's balance. // 2. Start the quoted call. Its ceiling is now held on the channel. // 3. While it runs, try an ordinary call that needs more than what is left. // It should be refused (402): held sats cannot be spent by other calls. // 4. When the quoted call settles, only its actual charge is taken and the // rest of the hold is released; the same ordinary call now runs. // // Costs two short calls (a few thousand sats). Needs Node 18+. Usage: // curl -O https://inference.bsvkey.com/escrow-check.mjs // BSVKEY_API_KEY= node escrow-check.mjs // PowerShell, after copy API key on the site: // $env:BSVKEY_API_KEY = (Get-Clipboard).Trim(); node escrow-check.mjs const BASE = process.env.BSVKEY_BASE || 'https://inference.bsvkey.com'; const KEY = (process.env.BSVKEY_API_KEY || '').trim(); if (!/^[^:\s]+:[^:\s]+$/.test(KEY)) { console.error('Set BSVKEY_API_KEY to channelId:channelSecret (copy API key on the site).'); process.exit(1); } const [channelId, secret] = KEY.split(':'); const MODEL = 'claude-haiku-4-5'; const post = async (path, body) => { const r = await fetch(BASE + path, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify(body) }); return { status: r.status, body: await r.json().catch(() => ({})) }; }; const balance = async () => (await (await fetch(`${BASE}/v1/channels/${channelId}`, { headers: { 'x-bsv-channel-secret': secret } })).json()).balanceSats; const manifest = (maxOutputTokens, maxBudgetSats) => ({ v: 'bsvkey.job-manifest/1', model: MODEL, maxInputTokens: 200, maxOutputTokens, maxBudgetSats, quoteDeadline: new Date(Date.now() + 10 * 60e3).toISOString(), nonce: 'escrow-' + Date.now() + Math.random().toString(16).slice(2, 8), }); const start = await balance(); if (!(start > 5000)) { console.error(`Channel balance is ${start} sat; this check needs more than 5,000.`); process.exit(1); } console.log(`1. channel balance: ${start} sat`); // Learn this channel's rates, then size a job whose ceiling leaves ~600 sat free. const probe = (await post('/v1/quote', { manifest: manifest(1, start) })).body.quote; if (!probe) { console.error('quote failed'); process.exit(1); } const maxOut = Math.floor(((start - 600) * 1000 - 200 * probe.rateInPer1k) / probe.rateOutPer1k); const q = await post('/v1/quote', { manifest: manifest(maxOut, start) }); if (q.status !== 200) { console.error('quote failed:', q.status, q.body); process.exit(1); } const quote = q.body.quote; console.log(`2. quote: up to ${maxOut} output tokens, ceiling ${quote.maxChargeSats} sat (leaves ${start - quote.maxChargeSats} sat free)`); const quoted = post('/v1/chat/completions', { messages: [{ role: 'user', content: 'In about 120 words, explain what an escrow is.' }], x_bsv_quote: quote }); await new Promise((r) => setTimeout(r, 1200)); const ordinary = { messages: [{ role: 'user', content: 'Reply with the single word OK.' }], max_tokens: 400, model: MODEL }; const blocked = await post('/v1/chat/completions', ordinary); const blockedMsg = blocked.body.error?.message || blocked.body.error || ''; console.log(`3. ordinary call while the ceiling is held: ${blocked.status} ${blocked.status === 402 ? 'refused ✓ (' + blockedMsg + ')' : 'NOT refused'}`); const done = await quoted; if (done.status !== 200) { console.error('quoted call failed:', done.status, done.body); process.exit(1); } const charged = done.body.x_bsv.usageReceipt.sats; const afterQuoted = await balance(); const extra = blocked.status === 200 ? blocked.body.x_bsv.usageReceipt.sats : 0; console.log(`4. quoted call charged ${charged} sat of a ${quote.maxChargeSats} sat ceiling; balance ${afterQuoted} sat (${start} - ${charged}${extra ? ' - ' + extra : ''} = ${start - charged - extra}) ${afterQuoted === start - charged - extra ? '✓' : '✗'}`); const again = await post('/v1/chat/completions', ordinary); console.log(`5. same ordinary call after release: ${again.status} ${again.status === 200 ? 'runs ✓ (charged ' + again.body.x_bsv.usageReceipt.sats + ' sat)' : JSON.stringify(again.body).slice(0, 200)}`); console.log(blocked.status === 402 && again.status === 200 ? '\nPASS: the quote ceiling was held during the call and released after.' : '\nNot conclusive: if step 3 was not refused, the quoted call may not have started yet. Run it again.');