openzoo 0.44.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/openzoo.js CHANGED
@@ -192,6 +192,17 @@ async function main() {
192
192
  case 'balance':
193
193
  await (await import('../lib/info.js')).printBalance();
194
194
  break;
195
+ case 'topup':
196
+ case 'prepay':
197
+ await (await import('../lib/info.js')).topUp(process.argv[3]);
198
+ break;
199
+ case 'credit':
200
+ case 'credits': {
201
+ const bal = await (await import('../lib/info.js')).creditBalance();
202
+ console.log(`prepaid credit: $${bal.toFixed(6)}`);
203
+ if (bal <= 0) console.log('buy some with: npx openzoo topup 10');
204
+ break;
205
+ }
195
206
  case 'address':
196
207
  (await import('../lib/info.js')).printAddress();
197
208
  break;
package/lib/info.js CHANGED
@@ -73,3 +73,57 @@ export async function printBalance() {
73
73
  console.log(` or USDC on Base to ${evmAddress}`);
74
74
  }
75
75
  }
76
+
77
+ /**
78
+ * PREPAY. Buys gateway credit in ONE settlement so later calls skip the
79
+ * per-call payment round trip entirely.
80
+ *
81
+ * That round trip is where the latency lives, not the model: MEASURED against
82
+ * the live gateway, a 402 challenge comes back in 0.12s while a full paid call
83
+ * takes 9-37s end to end. The gateway already applies credit automatically
84
+ * whenever a balance covers the quote — nothing could BUY it until now.
85
+ *
86
+ * Credit is keyed by the signed namespace, so it belongs to this wallet and
87
+ * cannot be spent by anyone else.
88
+ */
89
+ export async function topUp(usdArg) {
90
+ const usd = Number(usdArg);
91
+ if (!Number.isFinite(usd) || usd < 1 || usd > 500) {
92
+ throw new Error('usage: openzoo topup <usd> (1-500)');
93
+ }
94
+ const { PayClient } = await import('./pay.js');
95
+ const client = new PayClient();
96
+ const url = `${config.apiBase}/v1/credits/topup`;
97
+
98
+ const before = await creditBalance();
99
+ console.log(`credit before: $${before.toFixed(6)}`);
100
+ console.log(`buying $${usd.toFixed(2)} of credit — one on-chain settlement...`);
101
+
102
+ const { response, paid } = await client.fetch(url, {
103
+ method: 'POST',
104
+ headers: { 'content-type': 'application/json' },
105
+ body: JSON.stringify({ usd }),
106
+ });
107
+ const body = await response.json().catch(() => ({}));
108
+ if (!response.ok) {
109
+ throw new Error(`topup failed (HTTP ${response.status}): ${body.error || body.detail || 'unknown'}`);
110
+ }
111
+ console.log('');
112
+ console.log(`credited: $${Number(body.creditedUsd ?? usd).toFixed(2)}${paid ? '' : ' (from existing credit)'}`);
113
+ console.log(`balance: $${Number(body.balanceUsd ?? 0).toFixed(6)}`);
114
+ if (body.tx) console.log(`tx: ${body.tx}`);
115
+ console.log('');
116
+ console.log('calls now settle against this balance instead of paying on-chain each time.');
117
+ }
118
+
119
+ /** Current prepaid credit for this wallet's namespace. */
120
+ export async function creditBalance() {
121
+ const { withNamespace } = await import('./namespace.js');
122
+ try {
123
+ const r = await fetch(`${config.apiBase}/v1/credits`, { headers: withNamespace({}) });
124
+ const j = await r.json();
125
+ return Number(j.balanceUsd) || 0;
126
+ } catch {
127
+ return 0;
128
+ }
129
+ }
package/lib/proxy.js CHANGED
@@ -779,6 +779,34 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
779
779
  server.listen(config.port, bindHost, resolve);
780
780
  });
781
781
 
782
+ // AUTO-PREPAY. Paying on-chain per call is where the latency lives: the
783
+ // gateway answers its 402 challenge in ~0.12s while a full settled call
784
+ // MEASURED 9-37s end to end. Credit is applied automatically server-side
785
+ // whenever a balance covers the quote, so buying it once makes every later
786
+ // call skip verify+settle entirely.
787
+ //
788
+ // Runs in the background — never block the listener on a payment — and only
789
+ // when this wallet actually has funds, so a fresh/empty wallet is untouched.
790
+ // Opt out with OPENZOO_NO_AUTOTOPUP=1; size it with OPENZOO_AUTOTOPUP_USD.
791
+ if (!process.env.OPENZOO_NO_AUTOTOPUP) {
792
+ (async () => {
793
+ try {
794
+ const { creditBalance, topUp } = await import('./info.js');
795
+ const have = await creditBalance();
796
+ const want = Number(process.env.OPENZOO_AUTOTOPUP_USD || 5);
797
+ // Only top up when nearly dry, so restarting the proxy does not keep
798
+ // buying credit on top of a healthy balance.
799
+ if (have >= Math.min(1, want)) return;
800
+ say(`prepaid credit $${have.toFixed(4)} — topping up $${want} so calls stop settling on-chain each time...`);
801
+ await topUp(want);
802
+ } catch (e) {
803
+ // A wallet with no funds, or a gateway that refuses, must never stop
804
+ // the proxy from serving: calls just fall back to paying per call.
805
+ say(`auto top-up skipped: ${String(e.message || e).slice(0, 120)}`);
806
+ }
807
+ })();
808
+ }
809
+
782
810
  if (!silent) {
783
811
  // VERSION IN THE BANNER, deliberately. `npx openzoo` can serve a STALE
784
812
  // cached copy — npx reuses a cache entry that matches the bare spec, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.44.1",
3
+ "version": "0.45.0",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",