@bolloon/bolloon-agent 0.5.2 → 0.5.3

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.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/dist/agents/chain/transfer-classify.js +99 -0
  3. package/dist/agents/chain/transfer-index.js +354 -0
  4. package/dist/agents/identity/address-binding.js +613 -0
  5. package/dist/agents/network-pulse.js +706 -27
  6. package/dist/agents/x402/direct-payment.js +689 -0
  7. package/dist/agents/x402/order-identity.js +408 -0
  8. package/dist/agents/x402/paid-info-store.js +17 -0
  9. package/dist/agents/x402/seller-signing.js +614 -0
  10. package/dist/agents/x402/seller-summary.js +398 -0
  11. package/dist/cli/identity-command.js +327 -2
  12. package/dist/cli/protocol-envelope.js +4 -0
  13. package/dist/cli/update-commands.js +41 -25
  14. package/dist/cli/x402-buyer-command.js +463 -0
  15. package/dist/cli/x402-seller-command.js +289 -0
  16. package/dist/cli-entry.js +21 -5
  17. package/dist/electron-build/agents/chain/transfer-classify.js +106 -0
  18. package/dist/electron-build/agents/chain/transfer-classify.js.map +1 -0
  19. package/dist/electron-build/agents/chain/transfer-index.js +400 -0
  20. package/dist/electron-build/agents/chain/transfer-index.js.map +1 -0
  21. package/dist/electron-build/agents/identity/address-binding.js +672 -0
  22. package/dist/electron-build/agents/identity/address-binding.js.map +1 -0
  23. package/dist/electron-build/agents/network-pulse.js +714 -28
  24. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  25. package/dist/electron-build/agents/x402/direct-payment.js +744 -0
  26. package/dist/electron-build/agents/x402/direct-payment.js.map +1 -0
  27. package/dist/electron-build/agents/x402/order-identity.js +458 -0
  28. package/dist/electron-build/agents/x402/order-identity.js.map +1 -0
  29. package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -1
  30. package/dist/electron-build/agents/x402/paid-info-store.js +18 -0
  31. package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
  32. package/dist/electron-build/agents/x402/seller-signing.js +682 -0
  33. package/dist/electron-build/agents/x402/seller-signing.js.map +1 -0
  34. package/dist/electron-build/utils/auto-update.js +2 -2
  35. package/dist/electron-build/utils/auto-update.js.map +1 -1
  36. package/dist/electron-build/utils/runtime-bootstrap.js +1 -1
  37. package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -1
  38. package/dist/electron-build/utils/update-manager.js +3 -3
  39. package/dist/electron-build/utils/update-manager.js.map +1 -1
  40. package/dist/electron-build/utils/version-identity.js +1 -1
  41. package/dist/electron-build/utils/version-identity.js.map +1 -1
  42. package/dist/electron-build/web/routes-x402-info.js +28 -1
  43. package/dist/electron-build/web/routes-x402-info.js.map +1 -1
  44. package/dist/index.js +3 -3
  45. package/dist/utils/auto-update.js +2 -2
  46. package/dist/utils/runtime-bootstrap.js +1 -1
  47. package/dist/utils/update-health.js +2 -2
  48. package/dist/utils/update-manager.js +3 -3
  49. package/dist/utils/version-identity.js +1 -1
  50. package/dist/web/bolloon-web.json +3 -3
  51. package/dist/web/routes-x402-info.js +28 -1
  52. package/dist/web/sw.js +1 -1
  53. package/package.json +2 -1
@@ -0,0 +1,289 @@
1
+ /**
2
+ * x402-seller-command.ts — `bolloon x402 pending list|show|sign|auth-init|key`
3
+ *
4
+ * 薄包装: 业务逻辑一律在 `agents/x402/seller-signing.ts`, 本文件只做
5
+ * ① 参数解析 ② 人类可读输出 ③ 失败时的可操作提示。
6
+ *
7
+ * 红线 (与 docs/wiki/x402-seller-signing.md 一致):
8
+ * · 私钥**只在本进程内存里**用于签名; 绝不打印、绝不落日志、绝不进命令行、绝不上服务器
9
+ * · 共享密钥只出现在 0600 文件与请求头里; 输出里只有 keyId 与指纹
10
+ * · 签名只走既有 `buildSignedEnvelope` → `ed25519Sign` (不新写一套签名实现)
11
+ * · 卖方不在线 ⇒ 如实报 "已付款待签名", 不假装能交付
12
+ */
13
+ import { sellerApiPaths, sellerAuthPath, sellerAuthFingerprint, generateSellerAuth, loadSellerAuth, saveSellerAuth, sellerHome, sellerClientRequest, pendingView, signPending, resolveSellerKey, paidInfoRetrievePath, } from '../agents/x402/seller-signing.js';
14
+ import { canonicalize, ed25519Verify } from '../agents/x402/paid-info-protocol.js';
15
+ const USAGE = `
16
+ bolloon x402 pending —— 卖方本机签名交付 (私钥不离开本机)
17
+
18
+ bolloon x402 pending list [--endpoint <url>] [--status awaiting_signature] [--json]
19
+ bolloon x402 pending show <pendingId> [--endpoint <url>] [--json]
20
+ bolloon x402 pending sign <pendingId> [--endpoint <url>] [--agent <agentId>] [--json]
21
+ bolloon x402 pending auth-init [--endpoint <url>] [--force]
22
+ bolloon x402 pending key [--agent <agentId>] [--json]
23
+
24
+ auth-init 生成 0600 共享密钥 (~/.bolloon/x402-seller-auth.json), 打印 keyId/指纹/安装提示
25
+ key 打印本机卖方公钥 (给服务器钉住; **只有公钥**, 私钥永不出机)
26
+ list/show 带 HMAC 认证拉取待办 (服务器只持公钥, 签不了信封 ⇒ 必须本机签)
27
+ sign 读本机内容 → 既有 ed25519Sign 签信封 → 回传服务器 → 本机自检验签
28
+
29
+ 选项: --endpoint <url> (也可在 auth 文件里存 endpoint) · --agent <agentId> · --json
30
+ `;
31
+ function flagValue(args, name) {
32
+ const i = args.indexOf(name);
33
+ if (i >= 0 && i + 1 < args.length && !args[i + 1].startsWith('--'))
34
+ return args[i + 1];
35
+ const eq = args.find((a) => a.startsWith(`${name}=`));
36
+ return eq ? eq.slice(name.length + 1) : undefined;
37
+ }
38
+ function firstPositional(args) {
39
+ for (let i = 0; i < args.length; i++) {
40
+ const a = args[i];
41
+ if (a.startsWith('--')) {
42
+ if (!a.includes('='))
43
+ i++;
44
+ continue;
45
+ }
46
+ return a;
47
+ }
48
+ return undefined;
49
+ }
50
+ /** endpoint: --endpoint > auth 文件 > env */
51
+ async function resolveEndpoint(args) {
52
+ const explicit = flagValue(args, '--endpoint') || process.env.BOLLOON_X402_SELLER_ENDPOINT;
53
+ if (explicit)
54
+ return explicit;
55
+ const cfg = await loadSellerAuth();
56
+ if (cfg?.endpoint)
57
+ return cfg.endpoint;
58
+ throw new Error('没有端点: 给 --endpoint <url>, 或先跑 `bolloon x402 pending auth-init --endpoint <url>` 把它存进 auth 文件');
59
+ }
60
+ function short(s, n = 20) {
61
+ const v = String(s ?? '');
62
+ return v.length > n ? `${v.slice(0, n)}…` : v;
63
+ }
64
+ // ─────────────────────────────────────────────────────────── 子命令
65
+ async function cmdList(args) {
66
+ const json = args.includes('--json');
67
+ const endpoint = await resolveEndpoint(args);
68
+ const auth = await loadSellerAuth();
69
+ if (!auth) {
70
+ console.error('❌ 本机没有共享密钥 (~/.bolloon/x402-seller-auth.json) — 先跑 `bolloon x402 pending auth-init --endpoint <url>`');
71
+ return 1;
72
+ }
73
+ const status = flagValue(args, '--status');
74
+ const urlPath = status ? `${sellerApiPaths().list}?status=${encodeURIComponent(status)}` : sellerApiPaths().list;
75
+ // 被签的只有 pathname (查询串不参与)
76
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().list, urlPath });
77
+ if (!r.ok) {
78
+ console.error(`❌ 拉取失败 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
79
+ if (r.json?.hint)
80
+ console.error(` ${r.json.hint}`);
81
+ return 1;
82
+ }
83
+ if (json) {
84
+ console.log(JSON.stringify(r.json, null, 2));
85
+ return 0;
86
+ }
87
+ const items = r.json.pending || [];
88
+ console.log(`卖方待办 (${endpoint}) — 共 ${r.json.total ?? items.length} 条, 待签名 ${r.json.awaitingSignature ?? '?'} 条`);
89
+ if (!items.length) {
90
+ console.log(' (空) 没有待签名的付款 —— 卖方不在线时买方只会拿到「已付款待签名」');
91
+ return 0;
92
+ }
93
+ for (const p of items) {
94
+ const v = pendingView(p);
95
+ console.log(` ${v.pendingId} [${v.status}] ${v.itemId} ${v.payment.amount} ${v.payment.currency}@${v.payment.network} ${v.createdAt}`);
96
+ console.log(` 内容哈希 ${v.contentHash} · 付款凭据哈希 ${v.payment.receiptHash} · mode=${v.payment.mode}`);
97
+ }
98
+ console.log(' 下一步: bolloon x402 pending show <pendingId> → sign <pendingId>');
99
+ return 0;
100
+ }
101
+ async function cmdShow(args) {
102
+ const json = args.includes('--json');
103
+ const id = firstPositional(args);
104
+ if (!id) {
105
+ console.error('用法: bolloon x402 pending show <pendingId>');
106
+ return 1;
107
+ }
108
+ const endpoint = await resolveEndpoint(args);
109
+ const auth = await loadSellerAuth();
110
+ if (!auth) {
111
+ console.error('❌ 本机没有共享密钥 — 先跑 auth-init');
112
+ return 1;
113
+ }
114
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().show(id) });
115
+ if (!r.ok) {
116
+ console.error(`❌ 取不到 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
117
+ return 1;
118
+ }
119
+ if (json) {
120
+ console.log(JSON.stringify(r.json, null, 2));
121
+ return 0;
122
+ }
123
+ const p = r.json.pending;
124
+ console.log(`待办 ${p.pendingId} [${p.status}]`);
125
+ console.log(` item ${p.itemId} (${p.title})`);
126
+ console.log(` 卖方 DID ${p.providerDid}`);
127
+ console.log(` 内容哈希 ${p.contentHash}${p.contentCid ? ` cid=${p.contentCid}` : ''}`);
128
+ console.log(` 来源声明 kind=${p.source?.kind} refs=${(p.source?.refs || []).length}`);
129
+ console.log(` 价格 ${p.price.amount} ${p.price.currency}@${p.price.network} → ${p.price.payTo}`);
130
+ console.log(` 付款 mode=${p.payment.mode} ${p.payment.amount} ${p.payment.currency}@${p.payment.network}`);
131
+ console.log(` 付款凭据哈希 ${p.payment.receiptHash} ← 签名会绑定它`);
132
+ console.log(` 付款凭据原文 ${short(p.payment.receipt, 48)} (回执原文, 本机要用它重建哈希)`);
133
+ if (p.payment.txHash)
134
+ console.log(` 链上 txHash ${p.payment.txHash}`);
135
+ if (p.payment.payer)
136
+ console.log(` 付款方 ${p.payment.payer}`);
137
+ console.log(` 创建/过期 ${p.createdAt} → ${p.expiresAt}`);
138
+ if (p.envelopeHash)
139
+ console.log(` 已签信封哈希 ${p.envelopeHash}`);
140
+ console.log(` 下一步: bolloon x402 pending sign ${p.pendingId}`);
141
+ return 0;
142
+ }
143
+ async function cmdSign(args) {
144
+ const json = args.includes('--json');
145
+ const id = firstPositional(args);
146
+ if (!id) {
147
+ console.error('用法: bolloon x402 pending sign <pendingId> [--agent <agentId>]');
148
+ return 1;
149
+ }
150
+ const endpoint = await resolveEndpoint(args);
151
+ const auth = await loadSellerAuth();
152
+ if (!auth) {
153
+ console.error('❌ 本机没有共享密钥 — 先跑 auth-init');
154
+ return 1;
155
+ }
156
+ // ① 拉这条待办 (服务器是待办的事实来源)
157
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().show(id) });
158
+ if (!r.ok) {
159
+ console.error(`❌ 取不到待办 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
160
+ return 1;
161
+ }
162
+ const pending = r.json.pending;
163
+ if (pending.status !== 'awaiting_signature') {
164
+ console.error(`❌ 这条待办状态是 ${pending.status} — 不能签 (已签过/已过期都不许重签)`);
165
+ return 1;
166
+ }
167
+ // ② 本机内容 (签名要绑内容哈希 ⇒ 没有内容就签不出来, 如实报错)
168
+ const { getStoredInfo } = await import('../agents/x402/paid-info-store.js');
169
+ const stored = await getStoredInfo(pending.itemId, sellerHome());
170
+ if (!stored) {
171
+ console.error(`❌ 本机没有 item ${pending.itemId} 的内容 (~/.bolloon/x402-info/) — 没法签 (签一个对不上的信封等于假交付)`);
172
+ return 1;
173
+ }
174
+ // ③ 本机钥匙: 必须是这条 item 的卖方 DID (**不新建钥匙**)
175
+ const agentId = flagValue(args, '--agent');
176
+ const key = await resolveSellerKey(pending.providerDid, { agentId });
177
+ if (!key) {
178
+ console.error(`❌ 本机找不到能给 ${pending.providerDid} 签名的私钥 (查过 ~/.bolloon/agent-keys/*.json 与 ~/.bolloon/identity.json)`);
179
+ return 1;
180
+ }
181
+ // ④ 签 (既有 ed25519Sign), ⑤ 本机自检验签 —— 不信"我签好了", 用公钥验一遍
182
+ const envelope = await signPending({ pending, item: stored.item, content: stored.content, keypair: { did: key.did, publicKey: key.publicKeyHex, privateKey: Buffer.from(key.privateKeyHex, 'hex') } });
183
+ const selfCheck = await ed25519Verify(envelope.proof.publicKeyHex, canonicalize(envelope.proof.payload), envelope.proof.signature);
184
+ // ⑥ 回传 (服务器会用钉住的公钥再验一次)
185
+ const post = await sellerClientRequest({ endpoint, auth }, { method: 'POST', path: sellerApiPaths().envelope(pending.pendingId), body: JSON.stringify({ envelope }) });
186
+ if (json) {
187
+ console.log(JSON.stringify({
188
+ ok: post.ok && selfCheck,
189
+ pendingId: pending.pendingId,
190
+ itemId: pending.itemId,
191
+ contentHash: envelope.proof.payload.contentHash,
192
+ receiptHash: envelope.proof.payload.receiptHash,
193
+ publicKeyHex: envelope.proof.publicKeyHex,
194
+ did: envelope.proof.did,
195
+ keySource: key.source,
196
+ selfCheckSignature: selfCheck,
197
+ server: { status: post.status, ok: post.ok, code: post.json?.code, envelopeHash: post.json?.envelopeHash },
198
+ envelope: post.ok ? undefined : envelope,
199
+ }, null, 2));
200
+ return post.ok && selfCheck ? 0 : 1;
201
+ }
202
+ console.log(`签名自检: ${selfCheck ? '✅ ed25519Verify 通过' : '❌ 验签不通过 (绝不回传这种信封)'}`);
203
+ console.log(` 签名 ${short(envelope.proof.signature, 28)} (base64, 完整值用 --json)`);
204
+ console.log(` 签名公钥 ${envelope.proof.publicKeyHex}`);
205
+ console.log(` 钥匙来源 ${key.source} (DID ${envelope.proof.did})`);
206
+ console.log(` 载荷绑定 itemId=${envelope.proof.payload.itemId} · contentHash=${short(envelope.proof.payload.contentHash, 24)} · receiptHash=${short(envelope.proof.payload.receiptHash, 24)}`);
207
+ if (!post.ok) {
208
+ console.error(`❌ 回传失败 (HTTP ${post.status}): ${post.json?.code || ''} ${post.json?.error || post.text.slice(0, 200)}`);
209
+ return 1;
210
+ }
211
+ if (!selfCheck)
212
+ return 1;
213
+ console.log(`✅ 已回传并收下 — pendingId=${pending.pendingId} envelopeHash=${post.json?.envelopeHash}`);
214
+ console.log(` 买方下一步: 用取件 token 取 (不要重放付款凭据 —— 会再跑一遍结算):`);
215
+ console.log(` GET ${paidInfoRetrievePath(pending.itemId, pending.pendingId)} → 200 + 信封, 然后 ed25519Verify / verifyEnvelope 离线验签`);
216
+ return 0;
217
+ }
218
+ async function cmdAuthInit(args) {
219
+ const endpoint = flagValue(args, '--endpoint') || process.env.BOLLOON_X402_SELLER_ENDPOINT;
220
+ const force = args.includes('--force');
221
+ const existing = await loadSellerAuth();
222
+ if (existing && !force) {
223
+ console.log(`已存在共享密钥 (keyId=${existing.keyId} 指纹 ${sellerAuthFingerprint(existing)}) — 换新密钥要显式 --force`);
224
+ console.log(` endpoint: ${existing.endpoint || '(未设置)'} · 文件 ${sellerAuthPath()}`);
225
+ return 0;
226
+ }
227
+ const cfg = generateSellerAuth(endpoint);
228
+ const file = await saveSellerAuth(cfg);
229
+ console.log('✅ 已生成共享密钥 (0600 文件, 值不打印)');
230
+ console.log(` keyId ${cfg.keyId}`);
231
+ console.log(` 指纹 ${sellerAuthFingerprint(cfg)} ← 核对两端是不是同一把就用它`);
232
+ console.log(` endpoint ${cfg.endpoint || '(未设置, 用 --endpoint 指定)'}`);
233
+ console.log(` 文件 ${file}`);
234
+ console.log('');
235
+ console.log('安装到服务器 (值只走 stdin, 不进命令行/不进日志/不进会话):');
236
+ console.log(` ssh <server> 'install -m600 -o bolloonpay -g bolloonpay /dev/stdin /opt/bolloon-pay/.bolloon/${'x402-seller-auth.json'}' < ${file}`);
237
+ console.log('卖方公钥钉钉子 (只有公钥):');
238
+ console.log(' bolloon x402 pending key > /tmp/seller-key.json # 再 scp 到服务器 /opt/bolloon-pay/.bolloon/seller-key.json');
239
+ return 0;
240
+ }
241
+ async function cmdKey(args) {
242
+ const json = args.includes('--json');
243
+ const agentId = flagValue(args, '--agent');
244
+ // 不指定 agent 时用身份文件 (与 routes-x402-info.ts 的 loadProviderKeypair 同源)
245
+ const did = process.env.BOLLOON_SELLER_DID || '';
246
+ const key = await resolveSellerKey(did, { agentId });
247
+ if (!key) {
248
+ console.error('❌ 本机找不到卖方身份 (~/.bolloon/identity.json 或 agent-keys/)');
249
+ return 1;
250
+ }
251
+ const out = { protocol: 'bolloon-x402-seller/1', did: key.did, publicKeyHex: key.publicKeyHex, note: '这是公钥 (可放服务器)。私钥不在此输出中。' };
252
+ if (json) {
253
+ console.log(JSON.stringify(out, null, 2));
254
+ return 0;
255
+ }
256
+ console.log(`卖方 DID ${out.did}`);
257
+ console.log(`公钥 (hex) ${out.publicKeyHex}`);
258
+ console.log(`来源 ${key.source}`);
259
+ console.log(`钉到服务器: /opt/bolloon-pay/.bolloon/seller-key.json`);
260
+ return 0;
261
+ }
262
+ // ─────────────────────────────────────────────────────────── 入口
263
+ export async function x402PendingCommand(args) {
264
+ const sub = args[0];
265
+ const rest = args.slice(1);
266
+ try {
267
+ switch (sub) {
268
+ case 'list': return await cmdList(args);
269
+ case 'show': return await cmdShow(rest);
270
+ case 'sign': return await cmdSign(rest);
271
+ case 'auth-init': return await cmdAuthInit(rest);
272
+ case 'key': return await cmdKey(rest);
273
+ case undefined:
274
+ case '--help':
275
+ case '-h':
276
+ console.log(USAGE.trim());
277
+ return sub === undefined ? 1 : 0;
278
+ default:
279
+ console.error(`未知 x402 pending 子命令: ${sub}`);
280
+ console.log(USAGE.trim());
281
+ return 1;
282
+ }
283
+ }
284
+ catch (e) {
285
+ console.error(`❌ ${String(e?.message || e).slice(0, 300)}`);
286
+ return 1;
287
+ }
288
+ }
289
+ export { USAGE as X402_PENDING_USAGE };
package/dist/cli-entry.js CHANGED
@@ -68,7 +68,9 @@ ${BOLD}命令:${RESET}
68
68
  bolloon setup 初始化向导 (你的称呼 + 模型供应商 + API key + 连通性测试)
69
69
  bolloon identity init 非交互建本机身份 (~/.bolloon/identity.json, 0600, 幂等; 新机器/第二实例用)
70
70
  bolloon identity show 看本机身份 (只出 DID/指纹, 绝不打印私钥)
71
- bolloon update [plan|status|history|now] 检查更新 / 计划 / 状态 / 历史 / 执行 (默认只检查)
71
+ bolloon identity bind-address 登记「地址 ↔ DID」绑定 (链下登记·双侧签名·可离线验签)
72
+ bolloon identity bindings 绑定库: list | show <file> | verify <file> | publish --out <path>
73
+ bolloon update [wait|force|--dry-run|status|history] 直接执行更新 (先打印计划与风险检查; 只看计划用 --dry-run)
72
74
  bolloon doctor 安装入口 + 版本事实 + 更新状态自洽性诊断
73
75
  bolloon runtime [plan|install] 运行时 (Node/npm/Git/Python) 检查与安装
74
76
  bolloon model [name] [model] 列出 / 切换模型供应商 (如: bolloon model deepseek deepseek-v4-flash)
@@ -84,6 +86,7 @@ ${BOLD}命令:${RESET}
84
86
  bolloon engine run <prompt> 委派任务给智能体
85
87
  bolloon x402 fetch <url> x402 自动支付 HTTP 请求
86
88
  bolloon x402 balance <address> 查询 x402 钱包余额
89
+ bolloon x402 pay <endpoint> 买方自己发 EIP-3009 授权转账 (--with-memo 带订单标识)
87
90
  bolloon mcp serve MCP server (stdio) —— 给 MCP 客户端接本机 Agent 能力
88
91
  bolloon mcp tools 列出 MCP 暴露的 tools/resources (只读清单)
89
92
 
@@ -94,9 +97,8 @@ ${BOLD}示例:${RESET}
94
97
  bolloon --web # 启动 Web UI
95
98
  bolloon --cli # 命令行模式
96
99
  bolloon model # 查看当前模型供应商
97
- bolloon update # 检查更新 (只读, 不改任何东西)
98
- bolloon update plan # 看更新计划与风险检查
99
- bolloon update now # 真正执行更新
100
+ bolloon update # 直接执行更新 (先打印计划与风险检查)
101
+ bolloon update --dry-run # 只看计划, 不动任何东西
100
102
  bolloon doctor # 我这台机器的 Bolloon 是否自洽
101
103
  bolloon network join --json # 入网 (失败也结构化: ok:false + code + next_action)
102
104
  bolloon task --request-id <id> "…" # 用给定幂等键跑 M1 任务 (同一 requestId 不会付两次)
@@ -247,12 +249,14 @@ function firstPositional(args) {
247
249
  }
248
250
  return undefined;
249
251
  }
250
- /** x402 子命令: fetch / balance */
252
+ /** x402 子命令: fetch / balance / pending (卖方本机签名交付) */
251
253
  async function handleX402Command(x402Args) {
252
254
  if (x402Args.length === 0) {
253
255
  console.log(`${BOLD}用法:${RESET}`);
254
256
  console.log(' bolloon x402 fetch <url> [options] # 自动处理 402 Payment Required');
255
257
  console.log(' bolloon x402 balance <address> [options] # 查询钱包余额');
258
+ console.log(' bolloon x402 pay <endpoint> [--with-memo] # 买方自己发 EIP-3009 授权转账 (nonce 带订单标识 BOL1)');
259
+ console.log(' bolloon x402 pending list|show|sign # 卖方本机签名交付 (私钥不离开本机)');
256
260
  console.log('');
257
261
  console.log(`${BOLD}选项:${RESET}`);
258
262
  console.log(' --private-key <0x...> 自动支付钱包私钥,也可用 X402_PRIVATE_KEY');
@@ -266,6 +270,18 @@ async function handleX402Command(x402Args) {
266
270
  }
267
271
  const sub = x402Args[0];
268
272
  const rest = x402Args.slice(1);
273
+ // 2026-09-28: 卖方本机签名交付 —— 待办队列 (list/show/sign/auth-init/key)
274
+ if (sub === 'pending') {
275
+ const { x402PendingCommand } = await import('./cli/x402-seller-command.js');
276
+ process.exit(await x402PendingCommand(rest));
277
+ return;
278
+ }
279
+ // 2026-09-29: 买方自己发 EIP-3009 transferWithAuthorization (nonce 带订单标识 BOL1)
280
+ if (sub === 'pay') {
281
+ const { x402PayCommand } = await import('./cli/x402-buyer-command.js');
282
+ process.exit(await x402PayCommand(rest));
283
+ return;
284
+ }
269
285
  if (sub === 'fetch') {
270
286
  const url = firstPositional(rest);
271
287
  if (!url) {
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * transfer-classify.ts — 转账索引的**纯**部分 (类型 + 分类 + 折算 + 汇总)
4
+ * =========================================================================
5
+ * 为什么单独一个文件: 这两半的**加载代价**完全不同 ——
6
+ * · 本文件: 零依赖 (不 import ethers / 不发 RPC / 不读盘) ⇒ 公开页/快照构建可以**静态**引入;
7
+ * · `transfer-index.ts`: 要 JSON-RPC provider (ethers) 与文件系统 ⇒ 只在**同步**路径上加载。
8
+ * 把纯函数放在这里, network-pulse 才不必为了 `formatUnits` 把 ethers 拖进网页进程。
9
+ *
10
+ * 分类口径 (leo 2026-09-29 实测拍板; **不许**只按"USDC 转到该地址"计数):
11
+ * · `escrow_settlement` from == escrow 合约 → 「escrow 退款/释放」(实测块 51685757 / 51686009
12
+ * 两笔 +0.001 与那两笔 Disputed→Refunded **逐块对应**) —— 钱退回, **不是收入**;
13
+ * · `self_transfer` from ∈ 自己地址集 (可配 ownAddresses) → 「转入(非销售)」;
14
+ * · `external_payment` 其余 → 「外部付款」(与卖方端点台账对上的加标「经 x402 流程」);
15
+ * · `outbound` 关注地址**付出** → 不进「链上转入」计数。
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.TRANSFER_ADDRESS_RE = exports.USDC_BY_CHAIN = exports.ERC20_TRANSFER_TOPIC = exports.TRANSFER_INDEX_SCHEMA_VERSION = void 0;
19
+ exports.formatUnits = formatUnits;
20
+ exports.classifyTransfer = classifyTransfer;
21
+ exports.summarizeTransfers = summarizeTransfers;
22
+ exports.TRANSFER_INDEX_SCHEMA_VERSION = 1;
23
+ /** Transfer(address indexed from, address indexed to, uint256 value) 的 topic0 */
24
+ exports.ERC20_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
25
+ /** Base 主网 USDC (缺省 token; 也可由 chain.json 的 tokenAddress / env 覆盖) */
26
+ exports.USDC_BY_CHAIN = {
27
+ 8453: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
28
+ 84532: '0x036cbd53842c5426634e7929541ec2318f3dcf7e',
29
+ };
30
+ /** 0x + 40 位十六进制 (地址形状; 与 explorer.ts 的同一套尺子) */
31
+ exports.TRANSFER_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
32
+ /** 原子单位 → 十进制字符串 (纯整数运算, 不经过浮点; 去掉末尾多余的 0) */
33
+ function formatUnits(atomic, decimals) {
34
+ const v = BigInt(typeof atomic === 'bigint' ? atomic : (String(atomic || '0').trim() || '0'));
35
+ const neg = v < 0n;
36
+ const abs = neg ? -v : v;
37
+ const d = Math.max(0, Math.floor(decimals));
38
+ const base = 10n ** BigInt(d);
39
+ const whole = (abs / base).toString();
40
+ const frac = (abs % base).toString().padStart(d, '0').replace(/0+$/, '');
41
+ const out = frac ? `${whole}.${frac}` : whole;
42
+ return neg ? `-${out}` : out;
43
+ }
44
+ /**
45
+ * 一条转账的**分类** (纯函数: 输入事实 + 配置, 输出分类)。
46
+ * 为什么在**读**的时候算而不是写索引时算: 分类规则 / 自己地址集是会变的
47
+ * (把 0xb4cb8009… 认成"自己钱包"之前, 它只是一个外部地址) ⇒ 历史行必须能被重新分类,
48
+ * 而不是把当时的判断固化进索引。索引里只存**链上事实** (from/to/value/块号)。
49
+ */
50
+ function classifyTransfer(e, cfg) {
51
+ const from = String(e.from || '').toLowerCase();
52
+ const to = String(e.to || '').toLowerCase();
53
+ const watch = new Set((cfg.watchAddresses || []).map((a) => String(a).toLowerCase()));
54
+ if (e.direction === 'out' || (watch.has(from) && !watch.has(to)))
55
+ return 'outbound';
56
+ const escrow = String(cfg.escrowAddress || '').toLowerCase();
57
+ if (escrow && from === escrow)
58
+ return 'escrow_settlement';
59
+ if ((cfg.ownAddresses || []).map((a) => String(a).toLowerCase()).includes(from))
60
+ return 'self_transfer';
61
+ return 'external_payment';
62
+ }
63
+ /** 已落盘转账索引 → 汇总 (纯函数; 不发网络、不读配置以外的东西) */
64
+ function summarizeTransfers(state, cfg) {
65
+ const live = (state.entries || []).filter((e) => !e.suspect);
66
+ const by_class = { escrow_settlement: 0, self_transfer: 0, external_payment: 0 };
67
+ const by_class_atomic = { escrow_settlement: '0', self_transfer: '0', external_payment: '0' };
68
+ let inbound = 0, inboundAtomic = 0n, outbound = 0, outboundAtomic = 0n, pending = 0;
69
+ for (const e of live) {
70
+ if (e.finality === 'observed')
71
+ pending++;
72
+ const v = BigInt(e.value || '0');
73
+ if (e.direction === 'out') {
74
+ outbound++;
75
+ outboundAtomic += v;
76
+ continue;
77
+ }
78
+ inbound++;
79
+ inboundAtomic += v;
80
+ const c = classifyTransfer(e, cfg);
81
+ if (c !== 'outbound') {
82
+ by_class[c]++;
83
+ by_class_atomic[c] = (BigInt(by_class_atomic[c]) + v).toString();
84
+ }
85
+ }
86
+ const decimals = Number.isInteger(state.tokenDecimals) ? state.tokenDecimals : (cfg.tokenDecimals ?? 6);
87
+ return {
88
+ configured: cfg.enabled !== false && !!((state.watchAddresses || cfg.watchAddresses || []).length),
89
+ reason: cfg.reason || '',
90
+ entries: live.length,
91
+ suspects: (state.entries || []).length - live.length,
92
+ inbound, inbound_atomic: inboundAtomic.toString(), inbound_display: formatUnits(inboundAtomic, decimals),
93
+ by_class, by_class_atomic,
94
+ outbound, outbound_atomic: outboundAtomic.toString(),
95
+ token_symbol: state.tokenSymbol || cfg.tokenSymbol || '',
96
+ token_decimals: decimals,
97
+ watch_addresses_count: (state.watchAddresses || cfg.watchAddresses || []).length,
98
+ own_addresses_count: (cfg.ownAddresses || []).length,
99
+ from_block: state.fromBlock,
100
+ last_synced_block: state.lastSyncedBlock,
101
+ last_synced_at: state.lastSyncedAt,
102
+ head_block: state.headBlock,
103
+ pending_observation: pending,
104
+ };
105
+ }
106
+ //# sourceMappingURL=transfer-classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-classify.js","sourceRoot":"","sources":["../../../../src/agents/chain/transfer-classify.ts"],"names":[],"mappings":"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"}