@genkaku/protocol 0.1.18 → 0.1.21

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/dist/bridge.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Libp2p } from 'libp2p';
2
+ import { type WalletControl } from './walletControl.ts';
2
3
  import type { Hex } from 'viem';
3
4
  import type { ModeManager, RoleMode } from './modeManager.ts';
4
5
  import type { NetEnvironment } from './netconfig.ts';
@@ -38,16 +39,12 @@ export type BridgeOptions = {
38
39
  workers?: string[];
39
40
  settle?: boolean;
40
41
  }) => void | Promise<void>;
42
+ /** Re-run discovery. The worker set is read at startup, so the host restarts
43
+ * its half; this callback returns before that happens (the reply must land). */
44
+ onRefresh?: () => void | Promise<void>;
41
45
  /** Wallet operations the panel drives. Key material only ever leaves over
42
- * loopback CORS, and only after an explicit confirmation flag. */
43
- wallet?: {
44
- address: () => string;
45
- exportKey: () => string;
46
- importKey: (privateKey: string) => void;
47
- rotate: () => void;
48
- /** testnet only; mints MockUSDC to the current user wallet */
49
- mint?: (amount: bigint) => Promise<string>;
50
- };
46
+ * the panel CORS gate, and only after an explicit confirmation flag. */
47
+ wallet?: WalletControl;
51
48
  /** P4.9h: derived credit per worker wallet (escrow events, not self-report).
52
49
  * Absent = offers keep whatever the worker claimed. */
53
50
  credit?: (wallets: string[]) => Map<string, {
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAGpC,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAc/B,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAgB,MAAM,EAAc,MAAM,YAAY,CAAA;AAI7D,uEAAuE;AACvE,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAA;KAAE,CAAC,CAAA;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,cAAc,CAAA;IACnB,WAAW,EAAE,WAAW,CAAA;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9B,OAAO,EAAE,MAAM,GAAG,CAAA;IAClB,yEAAyE;IACzE,MAAM,EAAE,OAAO,CAAA;IACf,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,wFAAwF;IACxF,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3F,wDAAwD;IACxD,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD;gFAC4E;IAC5E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF;qFACiF;IACjF,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC;6EACyE;IACzE,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB;4DACwD;IACxD,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,MAAM,EAAE,KACd,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CAClH,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAuJD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAsmBlF"}
package/dist/bridge.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { appendFileSync } from 'node:fs';
6
6
  import { createServer } from 'node:http';
7
7
  import { applyPanelCors } from "./cors.js";
8
+ import { runWalletAction } from "./walletControl.js";
8
9
  import { openEscrow, publicClientFor, readWorker, refundSession, settleSession, signReceiptPayload, walletAddress, walletClientFor, } from "./chain.js";
9
10
  import { dialWorkerAny, fetchOffers, requestSettleCosign } from "./dial.js";
10
11
  import { dedupeOffers, toWire } from "./offer.js";
@@ -153,14 +154,31 @@ export async function startOpenAiBridge(opts) {
153
154
  return offers;
154
155
  }
155
156
  async function board() {
156
- const rows = await Promise.all(opts.workers.map(async (addr) => {
157
- try {
158
- return (await offersOf(addr)).map(offer => ({ addr, offer }));
159
- }
160
- catch (error) {
161
- opts.log(`offers: ${addr} unreachable (${error instanceof Error ? error.message : String(error)})`);
162
- return [];
157
+ // One dial per PEER, not per address. The chain directory and the seed can
158
+ // both list the same worker (a LAN address plus a relay circuit), and
159
+ // dialing those in parallel makes libp2p race itself — both attempts then
160
+ // time out. Try the peer's addresses in order, direct first, and stop at
161
+ // the first board that answers.
162
+ const peers = new Map();
163
+ for (const addr of opts.workers) {
164
+ const key = peerIdOf(addr) ?? addr;
165
+ const list = peers.get(key);
166
+ if (list == null)
167
+ peers.set(key, [addr]);
168
+ else if (!list.includes(addr))
169
+ list.push(addr);
170
+ }
171
+ const rows = await Promise.all([...peers.values()].map(async (addrs) => {
172
+ const ordered = [...addrs].sort((a, b) => Number(a.includes('/p2p-circuit')) - Number(b.includes('/p2p-circuit')));
173
+ for (const addr of ordered) {
174
+ try {
175
+ return (await offersOf(addr)).map(offer => ({ addr, offer }));
176
+ }
177
+ catch (error) {
178
+ opts.log(`offers: ${addr} unreachable (${error instanceof Error ? error.message : String(error)})`);
179
+ }
163
180
  }
181
+ return [];
164
182
  }));
165
183
  return dedupeOffers(rows.flat());
166
184
  }
@@ -339,7 +357,13 @@ export async function startOpenAiBridge(opts) {
339
357
  : 1024, MAX_COMPLETION_TOKENS);
340
358
  let escrowId = '0';
341
359
  let walletC = null;
342
- if (opts.settle && picked.offer.workerWallet?.startsWith('0x')) {
360
+ // A zero quote is free by the operator's own listing. Opening an escrow of
361
+ // 0 reverts on-chain, and the worker's paywall accepts an unpriced session
362
+ // for exactly these models, so skip the chain entirely.
363
+ if (picked.offer.pricePerHour === 0n) {
364
+ opts.log(`session ${jobId}: ${picked.offer.model} is free — no escrow`);
365
+ }
366
+ else if (opts.settle && picked.offer.workerWallet?.startsWith('0x')) {
343
367
  walletC = walletClientFor(opts.env, opts.userKey());
344
368
  const opened = await openEscrow(publicClientFor(opts.env), walletC, opts.env, picked.offer.workerWallet, picked.offer.pricePerHour);
345
369
  escrowId = opened.escrowId.toString();
@@ -579,60 +603,10 @@ export async function startOpenAiBridge(opts) {
579
603
  failure(res, 400, 'invalid JSON body');
580
604
  return;
581
605
  }
582
- if (action === 'export') {
583
- // The key is the wallet: require an explicit acknowledgement so a
584
- // stray click (or a scripted call without intent) cannot read it.
585
- if (body.confirm !== true) {
586
- failure(res, 400, 'export requires confirm: true');
587
- return;
588
- }
606
+ const reply = await runWalletAction(action, body, opts.wallet);
607
+ if (reply.status === 200 && action === 'export')
589
608
  res.setHeader('cache-control', 'no-store');
590
- json(res, 200, { ok: true, address: opts.wallet.address(), privateKey: opts.wallet.exportKey() });
591
- return;
592
- }
593
- if (action === 'import') {
594
- const key = typeof body.privateKey === 'string' ? body.privateKey.trim() : '';
595
- if (key === '') {
596
- failure(res, 400, 'privateKey is required');
597
- return;
598
- }
599
- try {
600
- opts.wallet.importKey(key);
601
- }
602
- catch (error) {
603
- failure(res, 400, error instanceof Error ? error.message : String(error));
604
- return;
605
- }
606
- json(res, 200, { ok: true, address: opts.wallet.address(), restarting: true });
607
- return;
608
- }
609
- if (action === 'new') {
610
- opts.wallet.rotate();
611
- json(res, 200, { ok: true, address: opts.wallet.address(), restarting: true });
612
- return;
613
- }
614
- if (action === 'mint') {
615
- if (opts.wallet.mint == null) {
616
- failure(res, 400, 'test USDC mint is only available on base-sepolia');
617
- return;
618
- }
619
- const raw = body.amountUsdc === undefined ? '100' : String(body.amountUsdc);
620
- const amount = Math.round(Number(raw) * 1e6);
621
- if (!Number.isFinite(amount) || amount <= 0 || amount > 10_000e6) {
622
- failure(res, 400, 'amountUsdc must be between 0 and 10000');
623
- return;
624
- }
625
- try {
626
- const tx = await opts.wallet.mint(BigInt(amount));
627
- json(res, 200, { ok: true, tx, amountUsdc: Number(raw) });
628
- }
629
- catch (error) {
630
- failure(res, 500, error instanceof Error ? error.message : String(error));
631
- }
632
- return;
633
- }
634
- failure(res, 404, 'unknown wallet action');
635
- return;
609
+ json(res, reply.status, reply.doc);
636
610
  }
637
611
  if (req.method === 'POST' && (path === '/genkaku/settings' || path === '/genkaku/settings/')) {
638
612
  let body;
@@ -644,9 +618,19 @@ export async function startOpenAiBridge(opts) {
644
618
  return;
645
619
  }
646
620
  if (body.refresh === true) {
647
- // Drop the 10s offer cache so the next status/model read re-dials
648
- // every discovered worker right now.
621
+ // Drop the 10s offer cache, then ask the host to re-read the chain
622
+ // directory and the seed. Clearing the cache alone only re-dialed
623
+ // the workers we already knew about.
649
624
  cache.clear();
625
+ transports.clear();
626
+ if (opts.onRefresh != null) {
627
+ try {
628
+ await opts.onRefresh();
629
+ }
630
+ catch (error) {
631
+ opts.log(`refresh: restart refused (${error instanceof Error ? error.message : String(error)})`);
632
+ }
633
+ }
650
634
  }
651
635
  const patch = {};
652
636
  if (body.workers !== undefined) {
package/dist/index.d.ts CHANGED
@@ -30,5 +30,6 @@ export * from './registry.ts';
30
30
  export * from './rpc.ts';
31
31
  export * from './status.ts';
32
32
  export * from './userSession.ts';
33
+ export * from './walletControl.ts';
33
34
  export * from './workerSession.ts';
34
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -30,5 +30,6 @@ export * from "./registry.js";
30
30
  export * from "./rpc.js";
31
31
  export * from "./status.js";
32
32
  export * from "./userSession.js";
33
+ export * from "./walletControl.js";
33
34
  export * from "./workerSession.js";
34
35
  //# sourceMappingURL=index.js.map
package/dist/keys.d.ts CHANGED
@@ -14,6 +14,8 @@ export type KeyVault = {
14
14
  workerKey(): PrivKey;
15
15
  /** replace the user key (import / rotate); refuses while an env key wins */
16
16
  setUserKey(key: PrivKey): void;
17
+ /** replace the worker identity key (import / rotate); same env guard */
18
+ setWorkerKey(key: PrivKey): void;
17
19
  /** derived addresses only (renderer-safe mirror of the keys) */
18
20
  addresses(): {
19
21
  user: EthAddress;
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAG/B,MAAM,MAAM,OAAO,GAAG,GAAG,CAAA;AACzB,MAAM,MAAM,UAAU,GAAG,KAAK,MAAM,EAAE,CAAA;AAEtC;gFACgF;AAChF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B,CAAA;AASD,MAAM,MAAM,QAAQ,GAAG;IACrB,2DAA2D;IAC3D,OAAO,IAAI,OAAO,CAAA;IAClB,6DAA6D;IAC7D,SAAS,IAAI,OAAO,CAAA;IACpB,4EAA4E;IAC5E,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IAC9B,gEAAgE;IAChE,SAAS,IAAI;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,CAAA;IACrD,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;CACpC,CAAA;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB,GAAG,QAAQ,CAuHX;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQnD"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAG/B,MAAM,MAAM,OAAO,GAAG,GAAG,CAAA;AACzB,MAAM,MAAM,UAAU,GAAG,KAAK,MAAM,EAAE,CAAA;AAEtC;gFACgF;AAChF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B,CAAA;AASD,MAAM,MAAM,QAAQ,GAAG;IACrB,2DAA2D;IAC3D,OAAO,IAAI,OAAO,CAAA;IAClB,6DAA6D;IAC7D,SAAS,IAAI,OAAO,CAAA;IACpB,4EAA4E;IAC5E,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IAC9B,wEAAwE;IACxE,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IAChC,gEAAgE;IAChE,SAAS,IAAI;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,CAAA;IACrD,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;CACpC,CAAA;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB,GAAG,QAAQ,CAmIX;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQnD"}
package/dist/keys.js CHANGED
@@ -108,6 +108,17 @@ export function createKeyVault(opts) {
108
108
  vault.userKeyEnc = codec.encrypt(next).toString('base64');
109
109
  persist();
110
110
  },
111
+ setWorkerKey(key) {
112
+ // Same reasoning as setUserKey: an env key is process-scoped, so claiming
113
+ // a successful replacement while the old identity stays live would lie.
114
+ if (envWorker != null) {
115
+ throw new Error('the worker key comes from GENKAKU_WORKER_KEY; unset it before importing or rotating');
116
+ }
117
+ const next = normalizeKey(key);
118
+ workerKey = next;
119
+ vault.workerKeyEnc = codec.encrypt(next).toString('base64');
120
+ persist();
121
+ },
111
122
  get usingEnvOverrides() {
112
123
  return envUser != null || envWorker != null;
113
124
  },
@@ -1 +1 @@
1
- {"version":3,"file":"p2pNode.d.ts","sourceRoot":"","sources":["../src/p2pNode.ts"],"names":[],"mappings":"AAcA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAClD,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,MAAM,CAAA;AAIxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAE5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,OAAO,EAAoB,KAAK,KAAK,EAAU,MAAM,YAAY,CAAA;AAajE,OAAO,KAAK,EAAgB,YAAY,EAAiB,MAAM,oBAAoB,CAAA;AAGnF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,eAAO,MAAM,iBAAiB,2JAQpB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,WAAW,CAAA;IACxB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,+EAA+E;IAC/E,OAAO,EAAE,KAAK,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;kFAC8E;IAC9E,SAAS,CAAC,EAAE,GAAG,CAAA;IACf;6EACyE;IACzE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,CAAA;AAED;0EAC0E,CAAA,MAAM,MAAM,aAAa,GAAG;IACpG,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,SAAS,EAAE,KAAK,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,KAAK,MAAM,EAAE,CAAA;CAC1B,CAAA;AAkBD,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,MAAM,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAY3E;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,MAAM,EAAE,CAAA;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,cAAc,EAAE,MAAM,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,SAAS,EAAE,MAAM,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAA;CACtC,CAAA;AAOD,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA8gBjF"}
1
+ {"version":3,"file":"p2pNode.d.ts","sourceRoot":"","sources":["../src/p2pNode.ts"],"names":[],"mappings":"AAcA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAClD,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,MAAM,CAAA;AAIxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAE5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,OAAO,EAAoB,KAAK,KAAK,EAAU,MAAM,YAAY,CAAA;AAajE,OAAO,KAAK,EAAgB,YAAY,EAAiB,MAAM,oBAAoB,CAAA;AAGnF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,eAAO,MAAM,iBAAiB,2JAQpB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,WAAW,CAAA;IACxB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,+EAA+E;IAC/E,OAAO,EAAE,KAAK,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;kFAC8E;IAC9E,SAAS,CAAC,EAAE,GAAG,CAAA;IACf;6EACyE;IACzE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,CAAA;AAED;0EAC0E,CAAA,MAAM,MAAM,aAAa,GAAG;IACpG,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,SAAS,EAAE,KAAK,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,KAAK,MAAM,EAAE,CAAA;CAC1B,CAAA;AAkBD,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,MAAM,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAY3E;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,MAAM,EAAE,CAAA;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,cAAc,EAAE,MAAM,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,SAAS,EAAE,MAAM,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAA;CACtC,CAAA;AAOD,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA+gBjF"}
package/dist/p2pNode.js CHANGED
@@ -167,6 +167,7 @@ export async function createSidecar(opts) {
167
167
  jobId: env.jobId,
168
168
  escrowId: env.escrowId || '0',
169
169
  clientPeer: env.clientPeer ?? '',
170
+ model: env.model,
170
171
  }).catch(() => ({ ok: false, code: 'paywall-error', reason: 'paywall check failed' }));
171
172
  if (!verdict.ok) {
172
173
  opts.sessionEvents?.({
package/dist/paywall.d.ts CHANGED
@@ -4,6 +4,8 @@ export type PaywallRequest = {
4
4
  jobId: string;
5
5
  escrowId: string;
6
6
  clientPeer: string;
7
+ /** the model in the OPEN envelope; decides whether "unpriced" is free */
8
+ model: string;
7
9
  };
8
10
  export type PaywallVerdict = {
9
11
  ok: true; /** the escrow's payer — receipts must be signed by this wallet */
@@ -21,6 +23,10 @@ export type PaywallOptions = {
21
23
  worker: `0x${string}`;
22
24
  /** dev escape hatch: allow `escrowId: "0"` (unpriced) sessions */
23
25
  allowUnpriced?: boolean;
26
+ /** the operator's current quote per model (micro-USDC/hour). An unpriced
27
+ * session is authorized only for a model quoted at exactly zero — a priced
28
+ * model must still present real escrow, so naming "0" cannot skip payment. */
29
+ prices?: (model: string) => bigint | undefined;
24
30
  now?: () => number;
25
31
  log?: {
26
32
  warn: (msg: string, ...args: unknown[]) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../src/paywall.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC,CAAC,kEAAkE;IAAC,KAAK,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,GACrG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,CAAA;AAK1E,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,aAAa,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;IACnB,iDAAiD;IACjD,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CAC1D,CAAA;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CA4C3D"}
1
+ {"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../src/paywall.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC,CAAC,kEAAkE;IAAC,KAAK,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,GACrG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,CAAA;AAK1E,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,aAAa,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;IACnB,iDAAiD;IACjD,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;mFAE+E;IAC/E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CAC1D,CAAA;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAgD3D"}
package/dist/paywall.js CHANGED
@@ -28,9 +28,12 @@ export function createPaywall(opts) {
28
28
  return async (request) => {
29
29
  const raw = (request.escrowId ?? '').trim();
30
30
  if (raw === '' || raw === '0') {
31
- // Unpriced sessions have no payer to certify; the dev escape hatch keeps
32
- // them working for an explicit opt-out only.
33
- return opts.allowUnpriced === true
31
+ // Unpriced sessions have no payer to certify. Two ways in: the operator
32
+ // disabled the paywall outright (dev), or this model's own quote is 0 —
33
+ // a free listing cannot open a 0-value escrow because the contract
34
+ // reverts, so free has to mean "no escrow".
35
+ const price = request.model === '' ? undefined : opts.prices?.(request.model);
36
+ return opts.allowUnpriced === true || price === 0n
34
37
  ? { ok: true, payer: ZERO }
35
38
  : { ok: false, code: 'payment-required', reason: 'unpriced session rejected: open an escrow first' };
36
39
  }
package/dist/status.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type WalletControl } from './walletControl.ts';
1
2
  import type { RoleMode } from './modeManager.ts';
2
3
  import type { OfferWire } from './offer.ts';
3
4
  /** One offer row rendered by the dashboard. */
@@ -26,6 +27,11 @@ export type StatusSnapshot = {
26
27
  transport?: 'direct' | 'relay' | 'lan';
27
28
  /** Relay hops this worker reserved (seed VM), for the panel to explain relay mode. */
28
29
  relayHints?: string[];
30
+ /** Live wallet figures, base units as decimal strings (wei / 6-dec USDC). */
31
+ balances?: {
32
+ eth: string;
33
+ usdc: string;
34
+ };
29
35
  };
30
36
  export type StatusHandle = {
31
37
  url: string;
@@ -60,5 +66,19 @@ export type OllamaControl = {
60
66
  remove: (model: string) => Promise<void>;
61
67
  };
62
68
  /** Serve the dashboard and live status JSON on 127.0.0.1. */
63
- export declare function startStatusServer(port: number, snapshot: () => StatusSnapshot | Promise<StatusSnapshot>, onCatalog?: CatalogUpdateHandler, onMode?: ModeUpdateHandler, ollama?: OllamaControl, onPublish?: PublishUpdateHandler): Promise<StatusHandle>;
69
+ /** Everything the worker dashboard can drive. Named fields rather than a long
70
+ * positional list: the wallet joined five other callbacks, and a positional
71
+ * boolean trade at the end is how panels end up wiring the wrong handler. */
72
+ export type StatusServerOptions = {
73
+ port: number;
74
+ snapshot: () => StatusSnapshot | Promise<StatusSnapshot>;
75
+ onCatalog?: CatalogUpdateHandler;
76
+ onMode?: ModeUpdateHandler;
77
+ ollama?: OllamaControl;
78
+ onPublish?: PublishUpdateHandler;
79
+ /** Worker identity wallet: export/import/rotate. No mint — a worker earns
80
+ * USDC and only ever needs gas, which it cannot buy from a faucet button. */
81
+ wallet?: WalletControl;
82
+ };
83
+ export declare function startStatusServer(opts: StatusServerOptions): Promise<StatusHandle>;
64
84
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,+CAA+C;AAC/C,MAAM,MAAM,WAAW,GAAG;IACxB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAA;IAClB,2EAA2E;IAC3E,cAAc,EAAE,OAAO,CAAA;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,iFAAiF;IACjF,SAAS,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAA;IACtC,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAED,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,CAAA;AAED,6DAA6D;AAC7D,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEzF,0DAA0D;AAC1D,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAExE,2DAA2D;AAC3D,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE7E,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC1B,mEAAmE;IACnE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,2CAA2C;IAC3C,aAAa,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,sEAAsE;IACtE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1H,0CAA0C;IAC1C,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC,CAAA;AAyKD,6DAA6D;AAC7D,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,SAAS,CAAC,EAAE,oBAAoB,EAChC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,aAAa,EACtB,SAAS,CAAC,EAAE,oBAAoB,GAC/B,OAAO,CAAC,YAAY,CAAC,CA+LvB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
package/dist/status.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // the JSON always reflect the live sidecar, never a boot-time copy.
4
4
  import { createServer } from 'node:http';
5
5
  import { applyPanelCors } from "./cors.js";
6
+ import { runWalletAction } from "./walletControl.js";
6
7
  /** Self-contained dashboard; mirrors the user bridge style and polls the JSON. */
7
8
  const DASHBOARD_HTML = `<!doctype html>
8
9
  <html lang="en">
@@ -165,8 +166,8 @@ function parseCatalog(body) {
165
166
  }
166
167
  return { ok: true, catalog };
167
168
  }
168
- /** Serve the dashboard and live status JSON on 127.0.0.1. */
169
- export async function startStatusServer(port, snapshot, onCatalog, onMode, ollama, onPublish) {
169
+ export async function startStatusServer(opts) {
170
+ const { port, snapshot, onCatalog, onMode, ollama, onPublish, wallet } = opts;
170
171
  const server = createServer((req, res) => {
171
172
  void (async () => {
172
173
  try {
@@ -246,6 +247,28 @@ export async function startStatusServer(port, snapshot, onCatalog, onMode, ollam
246
247
  json(res, { ok: true });
247
248
  return;
248
249
  }
250
+ if (req.method === 'POST' && path.startsWith('/genkaku/wallet/')) {
251
+ const action = path.slice('/genkaku/wallet/'.length).replace(/\/$/, '');
252
+ if (wallet == null) {
253
+ failure(res, 501, 'wallet operations are not enabled in this role');
254
+ return;
255
+ }
256
+ let body = {};
257
+ try {
258
+ body = await readJson(req);
259
+ }
260
+ catch {
261
+ failure(res, 400, 'invalid JSON body');
262
+ return;
263
+ }
264
+ const reply = await runWalletAction(action, body, wallet);
265
+ res.writeHead(reply.status, {
266
+ 'content-type': 'application/json',
267
+ ...(action === 'export' ? { 'cache-control': 'no-store' } : {}),
268
+ });
269
+ res.end(JSON.stringify(reply.doc));
270
+ return;
271
+ }
249
272
  if (path === '/genkaku/ollama' || path === '/genkaku/ollama/') {
250
273
  if (ollama == null) {
251
274
  failure(res, 501, 'ollama onboarding is not enabled in this role');
@@ -0,0 +1,17 @@
1
+ /** Wallet operations a control plane may offer. */
2
+ export type WalletControl = {
3
+ address: () => string;
4
+ exportKey: () => string;
5
+ importKey: (privateKey: string) => void | Promise<void>;
6
+ rotate: () => void | Promise<void>;
7
+ /** base-sepolia only; mints MockUSDC to the panel's own address */
8
+ mint?: (amount: bigint) => Promise<string>;
9
+ };
10
+ /** One action's HTTP answer: status code plus JSON body. */
11
+ export type WalletActionReply = {
12
+ status: number;
13
+ doc: Record<string, unknown>;
14
+ };
15
+ /** Validate and run one `/genkaku/wallet/<action>` call. */
16
+ export declare function runWalletAction(action: string, body: Record<string, unknown>, wallet: WalletControl): Promise<WalletActionReply>;
17
+ //# sourceMappingURL=walletControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walletControl.d.ts","sourceRoot":"","sources":["../src/walletControl.ts"],"names":[],"mappings":"AAYA,mDAAmD;AACnD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,MAAM,CAAA;IACvB,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD,MAAM,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,mEAAmE;IACnE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC3C,CAAA;AAED,4DAA4D;AAC5D,MAAM,MAAM,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAA;AAEhF,4DAA4D;AAC5D,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAwD5B"}
@@ -0,0 +1,63 @@
1
+ // GENKAKU: wallet operations shared by both control planes.
2
+ //
3
+ // The user bridge and the worker status server expose the same four actions —
4
+ // export, import, new, mint — against different keys: the buyer's key signs
5
+ // escrows, the worker's key signs receipts and binds the directory entry. The
6
+ // HTTP shape and the safeguards are identical, so they live here instead of
7
+ // being copied into two request handlers that can drift apart.
8
+ //
9
+ // Key material only ever leaves over the panel CORS gate (see cors.ts), and
10
+ // export additionally requires `confirm: true` so a stray click — or a script
11
+ // that reached the port without intent — cannot read it.
12
+ /** Validate and run one `/genkaku/wallet/<action>` call. */
13
+ export async function runWalletAction(action, body, wallet) {
14
+ if (action === 'export') {
15
+ // The key IS the wallet: require an explicit acknowledgement.
16
+ if (body.confirm !== true)
17
+ return { status: 400, doc: { error: { message: 'export requires confirm: true' } } };
18
+ return {
19
+ status: 200,
20
+ doc: { ok: true, address: wallet.address(), privateKey: wallet.exportKey() },
21
+ };
22
+ }
23
+ if (action === 'import') {
24
+ const key = typeof body.privateKey === 'string' ? body.privateKey.trim() : '';
25
+ if (key === '')
26
+ return { status: 400, doc: { error: { message: 'privateKey is required' } } };
27
+ try {
28
+ await wallet.importKey(key);
29
+ }
30
+ catch (error) {
31
+ return { status: 400, doc: { error: { message: error instanceof Error ? error.message : String(error) } } };
32
+ }
33
+ return { status: 200, doc: { ok: true, address: wallet.address(), restarting: true } };
34
+ }
35
+ if (action === 'new') {
36
+ try {
37
+ await wallet.rotate();
38
+ }
39
+ catch (error) {
40
+ return { status: 500, doc: { error: { message: error instanceof Error ? error.message : String(error) } } };
41
+ }
42
+ return { status: 200, doc: { ok: true, address: wallet.address(), restarting: true } };
43
+ }
44
+ if (action === 'mint') {
45
+ if (wallet.mint == null) {
46
+ return { status: 400, doc: { error: { message: 'test USDC mint is only available on base-sepolia' } } };
47
+ }
48
+ const raw = body.amountUsdc === undefined ? '100' : String(body.amountUsdc);
49
+ const amount = Math.round(Number(raw) * 1e6);
50
+ if (!Number.isFinite(amount) || amount <= 0 || amount > 10_000e6) {
51
+ return { status: 400, doc: { error: { message: 'amountUsdc must be between 0 and 10000' } } };
52
+ }
53
+ try {
54
+ const tx = await wallet.mint(BigInt(amount));
55
+ return { status: 200, doc: { ok: true, tx, amountUsdc: Number(raw) } };
56
+ }
57
+ catch (error) {
58
+ return { status: 500, doc: { error: { message: error instanceof Error ? error.message : String(error) } } };
59
+ }
60
+ }
61
+ return { status: 404, doc: { error: { message: 'unknown wallet action' } } };
62
+ }
63
+ //# sourceMappingURL=walletControl.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genkaku/protocol",
3
- "version": "0.1.18",
3
+ "version": "0.1.21",
4
4
  "description": "GenKaKu protocol core: on-chain worker directory, USDC escrow billing, libp2p transport, Ollama lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",