@seasonkoh/webaz 0.1.28 → 0.1.29

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 (111) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +7 -6
  3. package/dist/currency.js +16 -0
  4. package/dist/deposit-rails.js +52 -0
  5. package/dist/direct-pay-aml-monitor.js +67 -0
  6. package/dist/direct-pay-aml-review.js +40 -0
  7. package/dist/direct-pay-base-bond-entry.js +5 -0
  8. package/dist/direct-pay-bond-rail-clearance.js +94 -0
  9. package/dist/direct-pay-compliance-ingress.js +145 -0
  10. package/dist/direct-pay-controls.js +136 -0
  11. package/dist/direct-pay-create.js +162 -0
  12. package/dist/direct-pay-deferral-quota.js +43 -0
  13. package/dist/direct-pay-disclosures.js +44 -0
  14. package/dist/direct-pay-eligibility.js +46 -0
  15. package/dist/direct-pay-fee-ar.js +241 -0
  16. package/dist/direct-pay-launch-readiness.js +108 -0
  17. package/dist/direct-pay-launch-summary.js +68 -0
  18. package/dist/direct-pay-ledger.js +94 -0
  19. package/dist/direct-receive-account-qr.js +77 -0
  20. package/dist/direct-receive-accounts.js +82 -0
  21. package/dist/direct-receive-deferral.js +142 -0
  22. package/dist/direct-receive-deposits.js +260 -0
  23. package/dist/direct-receive-payment-instruction.js +26 -0
  24. package/dist/fx-rates.js +71 -0
  25. package/dist/layer0-foundation/L0-1-database/schema.js +531 -1
  26. package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
  27. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +41 -0
  28. package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
  29. package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
  30. package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
  31. package/dist/layer1-agent/L1-1-mcp-server/server.js +18 -16
  32. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
  33. package/dist/ledger.js +12 -3
  34. package/dist/mcp.js +23 -4
  35. package/dist/merchant-bond-domain.js +64 -0
  36. package/dist/merchant-bond-exposure.js +78 -0
  37. package/dist/merchant-bond-watcher.js +26 -0
  38. package/dist/payment-rails.js +29 -0
  39. package/dist/product-verification.js +93 -0
  40. package/dist/pwa/acp-feed.js +3 -2
  41. package/dist/pwa/contract-fingerprint.js +3 -0
  42. package/dist/pwa/direct-pay-guards.js +20 -0
  43. package/dist/pwa/direct-pay-order-redaction.js +24 -0
  44. package/dist/pwa/endpoint-actions.js +3 -0
  45. package/dist/pwa/public/app-ai.js +10 -10
  46. package/dist/pwa/public/app-chat-poll.js +29 -0
  47. package/dist/pwa/public/app-create-kinds.js +17 -0
  48. package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
  49. package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
  50. package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
  51. package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
  52. package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
  53. package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
  54. package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
  55. package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
  56. package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
  57. package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
  58. package/dist/pwa/public/app-direct-pay.js +227 -0
  59. package/dist/pwa/public/app-discover.js +20 -20
  60. package/dist/pwa/public/app-external-links.js +32 -0
  61. package/dist/pwa/public/app-listings.js +4 -4
  62. package/dist/pwa/public/app-prelaunch-waz.js +39 -0
  63. package/dist/pwa/public/app-price.js +55 -0
  64. package/dist/pwa/public/app-product-media.js +15 -0
  65. package/dist/pwa/public/app-profile.js +6 -6
  66. package/dist/pwa/public/app-seller.js +5 -5
  67. package/dist/pwa/public/app-shop.js +19 -19
  68. package/dist/pwa/public/app.js +142 -146
  69. package/dist/pwa/public/i18n.js +398 -197
  70. package/dist/pwa/public/index.html +17 -0
  71. package/dist/pwa/public/openapi.json +495 -1
  72. package/dist/pwa/routes/admin-analytics.js +4 -2
  73. package/dist/pwa/routes/admin-direct-receive-deposits.js +321 -0
  74. package/dist/pwa/routes/buyer-feeds.js +2 -1
  75. package/dist/pwa/routes/chat.js +6 -1
  76. package/dist/pwa/routes/dashboards.js +2 -1
  77. package/dist/pwa/routes/direct-pay-availability.js +196 -0
  78. package/dist/pwa/routes/direct-pay-disclosure-acks.js +74 -0
  79. package/dist/pwa/routes/direct-pay-timeouts.js +71 -0
  80. package/dist/pwa/routes/direct-receive-accounts.js +155 -0
  81. package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
  82. package/dist/pwa/routes/fx.js +12 -0
  83. package/dist/pwa/routes/leaderboard.js +47 -9
  84. package/dist/pwa/routes/listings.js +2 -2
  85. package/dist/pwa/routes/logistics.js +4 -0
  86. package/dist/pwa/routes/manifests.js +38 -0
  87. package/dist/pwa/routes/me-data.js +5 -2
  88. package/dist/pwa/routes/orders-action.js +117 -9
  89. package/dist/pwa/routes/orders-create.js +4 -0
  90. package/dist/pwa/routes/orders-read.js +36 -0
  91. package/dist/pwa/routes/p2p-products.js +2 -2
  92. package/dist/pwa/routes/products-create.js +5 -3
  93. package/dist/pwa/routes/products-links.js +34 -0
  94. package/dist/pwa/routes/products-list.js +2 -1
  95. package/dist/pwa/routes/products-update.js +22 -2
  96. package/dist/pwa/routes/promoter.js +3 -0
  97. package/dist/pwa/routes/referral.js +4 -0
  98. package/dist/pwa/routes/returns.js +26 -1
  99. package/dist/pwa/routes/rewards-apply.js +10 -5
  100. package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
  101. package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
  102. package/dist/pwa/routes/shops.js +2 -1
  103. package/dist/pwa/routes/url-claim.js +2 -2
  104. package/dist/pwa/routes/users-public.js +8 -0
  105. package/dist/pwa/routes/wallet-read.js +3 -0
  106. package/dist/pwa/routes/webauthn.js +1 -1
  107. package/dist/pwa/server.js +59 -102
  108. package/dist/runtime/webaz-schema-helpers.js +104 -0
  109. package/dist/store-verification.js +77 -0
  110. package/dist/version.js +1 -1
  111. package/package.json +71 -2
@@ -20,6 +20,43 @@ export const VALID_TRANSITIONS = {
20
20
  requiresEvidence: false,
21
21
  description: '下单后付款前取消订单'
22
22
  },
23
+ // ── Direct Pay (Rail 1) 直付:只"支付"这一步变,之后汇入既有流程 ──────────────
24
+ // 设计稿 docs/modules/DIRECT-PAYMENT-MODULE-DESIGN.INTERNAL.md §4。
25
+ // 本金(货款)不经协议;协议不验证付款;买家声明已付款 → 汇入 accepted(卖家发货)。
26
+ 'created→direct_pay_window': {
27
+ allowedRoles: ['system'], // 直付下单路由锁定卖家费用质押后,以 system 显式推进(非超时驱动)
28
+ requiresEvidence: false,
29
+ description: 'Rail1 直付:卖家已质押平台费、展示收款方式,进入付款窗口'
30
+ },
31
+ 'direct_pay_window→accepted': {
32
+ allowedRoles: ['buyer'],
33
+ requiresEvidence: false,
34
+ description: 'Rail1 直付:买家声明"我已付款" → 汇入既有流程(卖家发货);协议不验证付款,谎报则卖家不发货(零损失自纠正)'
35
+ },
36
+ 'direct_pay_window→cancelled': {
37
+ allowedRoles: ['buyer', 'system'],
38
+ requiresEvidence: false,
39
+ description: 'Rail1 直付:付款前买家取消(或卖家请求取消经买家确认)→ 释放费用质押'
40
+ },
41
+ 'direct_pay_window→direct_expired_unconfirmed': {
42
+ // deadlineField 供【专属 cron(direct-pay-timeouts.ts)】读取;故意【不设 autoFaultState】→
43
+ // 通用 engine.checkTimeouts(需 deadlineField+autoFaultState 两者)绝不触发本转移,避免漏掉释放质押等副作用。
44
+ allowedRoles: ['system'],
45
+ deadlineField: 'direct_pay_window_deadline',
46
+ requiresEvidence: false,
47
+ description: 'Rail1 直付:付款窗口超时未标记 → 不静默关单,转可争议态(专属 cron 释放费用质押 + 停用收款指令)'
48
+ },
49
+ 'direct_expired_unconfirmed→disputed': {
50
+ allowedRoles: ['buyer'],
51
+ requiresEvidence: true,
52
+ evidenceHint: '上传付款凭证(证据分级:链上 tx / 银行回执 / 截图)',
53
+ description: 'Rail1 直付:买家"我确实付了" → 升级争议(证据级信誉裁决,本档无资金赔付)'
54
+ },
55
+ 'direct_expired_unconfirmed→cancelled': {
56
+ allowedRoles: ['buyer', 'system'],
57
+ requiresEvidence: false,
58
+ description: 'Rail1 直付:买家确认未付 / 宽限期后系统关单(终态)'
59
+ },
23
60
  // ── 卖家接单 ──────────────────────────────────────────────
24
61
  'paid→accepted': {
25
62
  allowedRoles: ['seller'],
@@ -243,6 +280,8 @@ export const CURRENT_RESPONSIBLE = {
243
280
  in_transit: 'logistics', // 等物流投递
244
281
  delivered: 'buyer', // 等买家确认
245
282
  disputed: 'arbitrator', // 等仲裁处理
283
+ direct_pay_window: 'buyer', // Rail1: 等买家付款并标记
284
+ direct_expired_unconfirmed: 'buyer', // Rail1: 等买家确认未付 或 升级争议
246
285
  };
247
286
  /** Phase 1 self-fulfill 覆盖表:seller 一人承担 shipped/picked_up/in_transit 全程 */
248
287
  export const CURRENT_RESPONSIBLE_SELF_FULFILL = {
@@ -275,6 +314,8 @@ export const ORDER_STATE_MEANINGS = {
275
314
  refunded_full: { zh: '仲裁裁全额退款,订单作废(终态)', en: 'arbitration full refund, order voided (terminal)' },
276
315
  dispute_dismissed: { zh: '争议被驳回(无效,终态)', en: 'dispute dismissed (terminal)' },
277
316
  expired: { zh: '订单超时自动失败(通用兜底,终态)', en: 'order expired (generic timeout, terminal)' },
317
+ direct_pay_window: { zh: 'Rail1 直付:已质押平台费,等买家付款(协议不持货款)', en: 'Rail1 direct-pay: fee-staked, awaiting buyer off-protocol payment (protocol holds no funds)' },
318
+ direct_expired_unconfirmed: { zh: 'Rail1 直付:付款窗口超时未标记(不静默关单,留争议/确认窗口)', en: 'Rail1 direct-pay: payment window expired unmarked (not silently closed; dispute/confirm window open)' },
278
319
  };
279
320
  /** 订单/争议生命周期契约 —— 集成方 agent 读它即懂"订单怎么流转 + 每步谁驱动 + 何时 + 含义"。 */
280
321
  export function orderLifecycleContract() {
@@ -303,7 +303,7 @@ async function getLiveStats() {
303
303
  const disputes = await n('SELECT COUNT(*) as n FROM disputes');
304
304
  const skills = await n("SELECT COUNT(*) as n FROM skills WHERE active = 1");
305
305
  const totalVolume = ((await dbOne("SELECT COALESCE(SUM(total_amount),0) as v FROM orders WHERE status = 'completed'"))?.v ?? 0);
306
- return { users, active_products: products, total_orders: orders, completed_orders: completed, total_disputes: disputes, active_skills: skills, total_volume_dcp: totalVolume };
306
+ return { users, active_products: products, total_orders: orders, completed_orders: completed, total_disputes: disputes, active_skills: skills, total_volume_waz: totalVolume };
307
307
  }
308
308
  catch {
309
309
  return null;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * MCP 命令行标志(--version / --help / --mode / --doctor)。
3
+ *
4
+ * 目的:让人 / 陌生 agent 在【不启动 stdio server】的前提下验证安装、看清运行模式与真网络可达性。
5
+ * 无标志 → cliQuickResponse 返回 null,mcp.ts 照常启动 server(既有 MCP 客户端从不传 argv → 零破坏)。
6
+ * 模式判定复用 resolveMode(network-mode.ts)—— 与 server.ts 同一真相源,--mode/--doctor 报的就是 server 实际用的。
7
+ */
8
+ import { SOFTWARE_VERSION } from '../../version.js';
9
+ import { resolveMode } from './network-mode.js';
10
+ const HELP = `webaz — WebAZ MCP server (@seasonkoh/webaz)
11
+
12
+ Usage:
13
+ npx @seasonkoh/webaz start the MCP stdio server (what MCP clients invoke)
14
+ npx @seasonkoh/webaz --version print version and exit
15
+ npx @seasonkoh/webaz --mode print the resolved run mode and exit
16
+ npx @seasonkoh/webaz --doctor print mode + config + live-network reachability, then exit
17
+ npx @seasonkoh/webaz --help show this help
18
+
19
+ Modes (RFC-003):
20
+ network WEBAZ_API_KEY set — can transact on the live webaz.xyz network
21
+ network_readonly no key (DEFAULT) — public reads hit live webaz.xyz; transactions need a key
22
+ sandbox explicit WEBAZ_MODE=sandbox — local SQLite, isolated from the live network
23
+
24
+ Env:
25
+ WEBAZ_API_KEY your api_key (get one at https://webaz.xyz/#welcome). Set → network mode.
26
+ WEBAZ_MODE force a mode: network | network_readonly | sandbox (sandbox is opt-in only)
27
+ WEBAZ_API_URL network endpoint (default https://webaz.xyz)`;
28
+ /** 同步、纯:处理 --version/-v · --help/-h · --mode。命中 → 返回要打印的字符串;无这些标志 → null(mcp.ts 照常启动 server)。 */
29
+ export function cliQuickResponse(argv, env) {
30
+ const has = (...names) => names.some(n => argv.includes(n));
31
+ if (has('--version', '-v'))
32
+ return SOFTWARE_VERSION;
33
+ if (has('--help', '-h'))
34
+ return HELP;
35
+ if (has('--mode'))
36
+ return resolveMode(env);
37
+ return null;
38
+ }
39
+ /** --doctor:模式 + 配置 + 真网络可达性。fetch 可注入便于测试;5s 超时保护,绝不 hang。sandbox 不探网络。 */
40
+ export async function runDoctor(env, fetchImpl = fetch) {
41
+ const mode = resolveMode(env);
42
+ const url = (env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
43
+ const hasKey = !!(env.WEBAZ_API_KEY ?? '');
44
+ const lines = [
45
+ `webaz doctor — @seasonkoh/webaz v${SOFTWARE_VERSION}`,
46
+ ` mode: ${mode}`,
47
+ ` api_key: ${hasKey ? 'set (network transactions enabled)' : 'not set (network read-only; set WEBAZ_API_KEY to transact)'}`,
48
+ ` api_url: ${url}`,
49
+ ];
50
+ if (mode === 'sandbox') {
51
+ lines.push(' reachability: skipped (sandbox = local SQLite, isolated from the live network)');
52
+ return lines.join('\n');
53
+ }
54
+ let reach;
55
+ try {
56
+ const r = await fetchImpl(`${url}/api/protocol-status`, { signal: AbortSignal.timeout(5000) });
57
+ reach = r.ok ? `ok (${url} reachable, HTTP ${r.status})` : `degraded (HTTP ${r.status})`;
58
+ }
59
+ catch (e) {
60
+ reach = `unreachable (${e.message})`;
61
+ }
62
+ lines.push(` reachability: ${reach}`);
63
+ return lines.join('\n');
64
+ }
@@ -7,6 +7,17 @@
7
7
  * the predicate so the gate decision is unit-testable without booting the stdio
8
8
  * server. (Extracted from server.ts; behavior unchanged except adding webaz_pair.)
9
9
  */
10
+ /**
11
+ * 解析运行模式(纯函数,单一真相源 —— server.ts 的 MODE + CLI 的 --mode/--doctor 都用它,杜绝漂移)。
12
+ * 显式 WEBAZ_MODE(network|network_readonly|sandbox)优先;否则有 api_key → network,无 key → network_readonly。
13
+ * sandbox 只能显式设置(无 key 【不】回落 sandbox)。key 的真伪判定与 server.ts 一致(WEBAZ_API_KEY ?? '' 的真值性)。
14
+ */
15
+ export function resolveMode(env) {
16
+ const m = (env.WEBAZ_MODE ?? '').toLowerCase();
17
+ if (m === 'network' || m === 'sandbox' || m === 'network_readonly')
18
+ return m;
19
+ return (env.WEBAZ_API_KEY ?? '') ? 'network' : 'network_readonly';
20
+ }
10
21
  // Tools that talk to the live webaz.xyz network (Bearer api_key where needed).
11
22
  // Un-listed tools run sandbox (local). `_mode` annotation = network for these.
12
23
  export const NETWORK_TOOLS = new Set([
@@ -13,8 +13,8 @@
13
13
  * webaz_wallet 查看钱包余额
14
14
  * …(39 工具,完整定义见下方 TOOLS 数组;数量以 TOOLS.length 为准)
15
15
  *
16
- * 双模(RFC-003):NETWORK(WEBAZ_API_KEY → 调 webaz.xyz)/ SANDBOX(本机库);见 NETWORK_TOOLS / apiCall / toolBackend。
17
- * 关联 / Related: AGENTS.md · RFC-003(双模) · RFC-004(webaz_feedback) · 元规则 #4 不撒谎(_mode 戳) /
16
+ * 三态(RFC-003):NETWORK(WEBAZ_API_KEY → 调 webaz.xyz)/ NETWORK 只读(无 key,公共读走 webaz.xyz)/ SANDBOX(仅 WEBAZ_MODE=sandbox,本机库);见 NETWORK_TOOLS / apiCall / toolBackend。
17
+ * 关联 / Related: AGENTS.md · RFC-003(三态:network / network_readonly / sandbox) · RFC-004(webaz_feedback) · 元规则 #4 不撒谎(_mode 戳) /
18
18
  * #6 不滥用(agent 责任制 + Iron-Rule 真人动作)· 生产端点在 src/pwa(NETWORK 共用)
19
19
  */
20
20
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -25,7 +25,7 @@ import { initDatabase, generateId } from '../../layer0-foundation/L0-1-database/
25
25
  import { setSeamDb } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(本进程注入)
26
26
  import { applyWebazRuntimeSchema } from '../../runtime/apply-webaz-runtime-schema.js'; // 与 PWA 同源的纯 schema 桥(防 MCP fresh DB 漂移)
27
27
  import { generateCodeVerifier, pkceChallengeS256 } from '../../runtime/agent-pairing.js'; // RFC-020 PR-C1 — PKCE 配对
28
- import { NETWORK_TOOLS, NETWORK_SELF_AWARE, toolAllowedInNetworkMode } from './network-mode.js'; // RFC-003 网络门(可单测)
28
+ import { NETWORK_TOOLS, NETWORK_SELF_AWARE, toolAllowedInNetworkMode, resolveMode } from './network-mode.js'; // RFC-003 网络门(可单测)+ 模式解析(单一真相源)
29
29
  import { homedir } from 'node:os';
30
30
  import { join as pathJoin } from 'node:path';
31
31
  import { existsSync as fsExists, mkdirSync, writeFileSync, readFileSync, unlinkSync, chmodSync } from 'node:fs';
@@ -45,7 +45,7 @@ const TELEMETRY_URL = process.env.WEBAZ_TELEMETRY_URL ?? 'https://webaz.xyz/api/
45
45
  // 2026-06-01: phase A pre-launch 默认 OFF(opt-in)— W8 public launch 时翻回 default ON + 加 README 披露段
46
46
  // Phase A pre-launch: telemetry default OFF (opt-in). Flip to default ON at W8 launch + add README disclosure section.
47
47
  const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() === 'on';
48
- // ─── RFC-003 P0: 双模(NETWORK / SANDBOX)骨架 ─────────────────────
48
+ // ─── RFC-003 P0: 三态(network_readonly / network / sandbox)骨架 ─────────────────────
49
49
  // NETWORK = 带 api_key 调 webaz.xyz/api(加入共享生产网络);SANDBOX = 本地 SQLite(离线试玩,与全网隔离)。
50
50
  // P0 不迁移任何工具(NETWORK_TOOLS 为空)→ 一切仍走本地 = 零行为变化;P1/P2 逐个把工具名加入集合切到网络。
51
51
  const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
@@ -59,15 +59,12 @@ export function resolveMcpApiKey(args, envKey = WEBAZ_API_KEY) {
59
59
  const explicit = typeof args?.api_key === 'string' ? args.api_key.trim() : '';
60
60
  return explicit || envKey;
61
61
  }
62
- const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
63
62
  // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
64
63
  // network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
65
64
  // 需身份的写/读返回"设 WEBAZ_API_KEY(到 #welcome 申请邀请)"。离线本地 playground 改为【显式】 WEBAZ_MODE=sandbox。
66
65
  // —— 治"装完=空沙盒劝退"的死首体验;route/guard 与 network 同路(见 isNetworkMode),只是无 Bearer + 文案不同。
67
- const MODE = WEBAZ_MODE_ENV === 'network' ? 'network'
68
- : WEBAZ_MODE_ENV === 'sandbox' ? 'sandbox'
69
- : WEBAZ_MODE_ENV === 'network_readonly' ? 'network_readonly'
70
- : (WEBAZ_API_KEY ? 'network' : 'network_readonly');
66
+ // 单一真相源:resolveMode(见 network-mode.ts),CLI --mode/--doctor 用同一函数,杜绝漂移。
67
+ const MODE = resolveMode(process.env);
71
68
  // network 或 network_readonly 都"走真网络"(后者无 Bearer)。sandbox 才是本地。
72
69
  const isNetworkMode = () => MODE === 'network' || MODE === 'network_readonly';
73
70
  const recentCalls = [];
@@ -1899,10 +1896,10 @@ async function handleInfo() {
1899
1896
  ? '🟢 NETWORK(只读):无 api_key。公共读(搜索/榜单/价格史/浏览)打 webaz.xyz 真网络(见 network_live)。要交易(注册/下单/上架等)请设 WEBAZ_API_KEY —— 到 ' + WEBAZ_API_URL + '/#welcome 申请邀请。'
1900
1897
  : '🟡 SANDBOX:所有工具都在本机本地 SQLite 运行,与 webaz.xyz 全网隔离(显式 dev/demo 模式)。任何计数 / 账号 / 订单仅本机有效。不设 WEBAZ_MODE 则默认走真网络只读。',
1901
1898
  phase: 'pre_launch',
1902
- real_users_on_canonical: 0,
1899
+ // 真实用户/规模【不在此硬编码】—— 网络模式下 network_live(上方,实时拉 /api/protocol-status)带有 real_users_on_canonical 真值。
1903
1900
  canonical_endpoint: 'https://webaz.xyz',
1904
1901
  economic_flow: 'simulated WAZ (test currency, 1 WAZ ≈ 1 USDC peg is a模拟基准, not a real exchange rate). No fiat/crypto settles yet.',
1905
- disclaimer: '本协议尚未公开上线,prod 真实用户≈0,无真实经济流转。下方 live_stats / 工具示例所有计数均来自**本机 MCP 服务器的本地 SQLite**(~/.webaz/webaz.db),仅供 dev / demo,不代表协议全网真实状态。请勿据此评估市场规模、做投资决策、或替终端用户承诺任何经济关系。',
1902
+ disclaimer: '本协议尚未公开上线(pre-launch),无真实经济流转。真实用户/规模请以 network_live(本次从 webaz.xyz 实时拉取的 /api/protocol-status,含 real_users_on_canonical)为准;下方 live_stats / 工具示例的所有计数均来自**本机 MCP 服务器的本地 SQLite**(~/.webaz/webaz.db),仅供 dev / demo,不代表协议全网真实状态。请勿据此评估市场规模、做投资决策、或替终端用户承诺任何经济关系。',
1906
1903
  },
1907
1904
  description: 'WebAZ is a decentralized commerce protocol. Every transaction flows through a state machine; each state transition needs proof-of-action from the responsible party. Any timeout-without-action: protocol auto-rules default + executes remedy. / WebAZ 是去中心化商业协议;每笔交易通过状态机流转,每次状态转移需对应责任方的操作证明;超时未操作则协议自动判违约并执行处置。',
1908
1905
  // 给【终端用户/买家】的价值主张 —— 陌生 agent / 普通买家先要知道"对我有什么用",而非只读抽象 tagline + 技术 description。
@@ -1996,7 +1993,7 @@ function handleRegister(args) {
1996
1993
  ],
1997
1994
  register_url: WEBAZ_API_URL,
1998
1995
  why_not_agent_self_register: 'agent 自助注册会绕过邀请 / captcha / 真人 Passkey 责任制,破坏协议的可问责性(#6 不滥用 / CHARTER §4 I-5)。',
1999
- want_to_try_offline_first: '只想离线试玩、暂不连网络?设环境变量 WEBAZ_MODE=sandbox(或清空 WEBAZ_API_KEY),webaz_register 会在本机沙盒建一个测试账号(仅本机有效)。',
1996
+ want_to_try_offline_first: '只想离线试玩、暂不连网络?设环境变量 WEBAZ_MODE=sandbox(本机沙盒需显式开启;无 key 仍是 NETWORK 只读,不会进沙盒),webaz_register 会在本机沙盒建一个测试账号(仅本机有效)。',
2000
1997
  };
2001
1998
  }
2002
1999
  // ─── SANDBOX 模式:本机建测试账号(显著标注,仅本机有效,与 webaz.xyz 全网隔离)──
@@ -2524,8 +2521,8 @@ async function handleListProduct(args) {
2524
2521
  id, seller_id, title, description, price, stock, category, stake_amount,
2525
2522
  specs, brand, model, source_price,
2526
2523
  ship_regions, handling_hours, estimated_days, fragile,
2527
- return_days, return_condition, warranty_days
2528
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2524
+ return_days, return_condition, warranty_days, currency
2525
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'WAZ')
2529
2526
  `).run(id, user.id, args.title, args.description, price, args.stock ?? 1, args.category ?? null, stakeAmount, specsJson, args.brand ?? null, args.model ?? null, args.source_price != null ? Number(args.source_price) : null, args.ship_regions ?? '全国', args.handling_hours != null ? Number(args.handling_hours) : 24, estJson, args.fragile ? 1 : 0, args.return_days != null ? Number(args.return_days) : 7, args.return_condition ?? '', args.warranty_days != null ? Number(args.warranty_days) : 0);
2530
2527
  // 注:per-order stake 模型不在此 lock — 移到 place_order 时按订单总额锁
2531
2528
  // S2/S3/S4 新字段:通过 HTTP PUT 走标准校验路径(i18n 语言白名单 / origin_claims 4KB+sha256 / 库存阈值)
@@ -3713,13 +3710,18 @@ async function handleReferral(args) {
3713
3710
  state = 'never_activated';
3714
3711
  note = 'Rewards inactive — attributions recorded; commissions held in pending_commission_escrow (30d window per protocol_params.rewards_opt_in.escrow_days) until you activate via PWA #me.';
3715
3712
  }
3716
- const pending = db.prepare("SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total FROM pending_commission_escrow WHERE recipient_user_id = ? AND status = 'pending'").get(userId);
3717
- const expired = db.prepare("SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total FROM pending_commission_escrow WHERE recipient_user_id = ? AND status = 'expired'").get(userId);
3713
+ // RFC-018: matures_at IS NULL = opt-out escrow only (clearing rows auto-mature; surfaced separately in PR3).
3714
+ const pending = db.prepare("SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total FROM pending_commission_escrow WHERE recipient_user_id = ? AND status = 'pending' AND matures_at IS NULL").get(userId);
3715
+ const expired = db.prepare("SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total FROM pending_commission_escrow WHERE recipient_user_id = ? AND status = 'expired' AND matures_at IS NULL").get(userId);
3716
+ // RFC-018 clearing window: opted-in commission accrued but not yet matured into the wallet
3717
+ // (matures_at NOT NULL). Distinct from opt-out escrow above — pure read, surfaced for the agent.
3718
+ const clearing = db.prepare("SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total FROM pending_commission_escrow WHERE recipient_user_id = ? AND status = 'pending' AND matures_at IS NOT NULL").get(userId);
3718
3719
  return {
3719
3720
  state,
3720
3721
  opted_in: optIn === 1,
3721
3722
  note,
3722
3723
  pending_escrow: { count: pending.n, total_amount: pending.total },
3724
+ clearing: { count: clearing.n, total_amount: clearing.total }, // RFC-018: accrued, maturing after the return window (not yet paid)
3723
3725
  expired_to_charity: { count: expired.n, total_amount: expired.total },
3724
3726
  spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
3725
3727
  };
@@ -5,7 +5,7 @@
5
5
  * 核心思路:解决冷启动——现有 Amazon/Shopify 卖家零成本接入新渠道。
6
6
  *
7
7
  * Skill 类型:
8
- * catalog_sync - 目录同步(把外部店铺接入 DCP 搜索)
8
+ * catalog_sync - 目录同步(把外部店铺接入 WebAZ 搜索)
9
9
  * auto_accept - 自动接单(买家下单后立即接受,无需等待)
10
10
  * price_negotiation - 价格协商(允许 Agent 在限定范围内议价)
11
11
  * quality_guarantee - 质量承诺(额外质押,增强买家信心)
@@ -67,8 +67,8 @@ export const SKILL_TYPE_META = {
67
67
  label: '目录同步',
68
68
  label_en: 'Catalog sync',
69
69
  icon: '🔄',
70
- description: '将卖家的外部商品目录(Amazon/Shopify/自定义)同步到 DCP,买家 Agent 订阅后可优先发现这些商品。',
71
- description_en: 'Sync seller external catalogs (Amazon/Shopify/custom) to DCP — subscribed buyer agents discover these first.',
70
+ description: '将卖家的外部商品目录(Amazon/Shopify/自定义)同步到 WebAZ,买家 Agent 订阅后可优先发现这些商品。',
71
+ description_en: 'Sync seller external catalogs (Amazon/Shopify/custom) to WebAZ — subscribed buyer agents discover these first.',
72
72
  },
73
73
  auto_accept: {
74
74
  label: '自动接单',
@@ -95,7 +95,7 @@ export const SKILL_TYPE_META = {
95
95
  label: '质量承诺',
96
96
  label_en: 'Quality guarantee',
97
97
  icon: '🛡️',
98
- description: '卖家额外质押 DCP 作为质量保证金,问题订单买家可获得额外赔偿。',
98
+ description: '卖家额外质押 WAZ 作为质量保证金,问题订单买家可获得额外赔偿。',
99
99
  description_en: 'Seller stakes extra WAZ as quality bond — buyer gets additional compensation on disputes.',
100
100
  },
101
101
  instant_ship: {
package/dist/ledger.js CHANGED
@@ -1,9 +1,18 @@
1
1
  import { toUnits, toDecimal } from './money.js';
2
2
  /** 读某用户钱包当前余额(整数 base-units)。无钱包行 → 全 0。 */
3
3
  export function walletUnits(db, userId) {
4
- const r = db.prepare('SELECT COALESCE(balance,0) balance, COALESCE(staked,0) staked, COALESCE(escrowed,0) escrowed, COALESCE(earned,0) earned FROM wallets WHERE user_id = ?')
5
- .get(userId);
6
- return { balance: toUnits(r?.balance ?? 0), staked: toUnits(r?.staked ?? 0), escrowed: toUnits(r?.escrowed ?? 0), earned: toUnits(r?.earned ?? 0) };
4
+ // fee_staked Direct Pay 新增列。向后兼容缺该列的库(迁移前 / 测试用最小 schema):
5
+ // 先按含 fee_staked 查;若该列不存在(no such column)→ 退回 4 列查,fee_staked=0。
6
+ let r;
7
+ try {
8
+ r = db.prepare('SELECT COALESCE(balance,0) balance, COALESCE(staked,0) staked, COALESCE(escrowed,0) escrowed, COALESCE(earned,0) earned, COALESCE(fee_staked,0) fee_staked FROM wallets WHERE user_id = ?')
9
+ .get(userId);
10
+ }
11
+ catch {
12
+ r = db.prepare('SELECT COALESCE(balance,0) balance, COALESCE(staked,0) staked, COALESCE(escrowed,0) escrowed, COALESCE(earned,0) earned FROM wallets WHERE user_id = ?')
13
+ .get(userId);
14
+ }
15
+ return { balance: toUnits(r?.balance ?? 0), staked: toUnits(r?.staked ?? 0), escrowed: toUnits(r?.escrowed ?? 0), earned: toUnits(r?.earned ?? 0), fee_staked: toUnits(r?.fee_staked ?? 0) };
7
16
  }
8
17
  /**
9
18
  * 对钱包字段施加整数单位 delta,以【绝对值】落库(读当前→加 delta→写 toDecimal)。
package/dist/mcp.js CHANGED
@@ -4,12 +4,31 @@
4
4
  *
5
5
  * 运行:npm run mcp(或 npx @seasonkoh/webaz);通过 stdio 与 Claude 等 MCP 客户端通信。
6
6
  * 工具实现都在 src/layer1-agent/L1-1-mcp-server/server.ts(本文件只是 bootstrap)。
7
- * 双模:配 WEBAZ_API_KEY → NETWORK( webaz.xyz 共享网络);否则 SANDBOX(本机库)。详见 RFC-003。
7
+ * 模式:配 WEBAZ_API_KEY → NETWORK(可交易,调 webaz.xyz 共享网络);无 key → NETWORK 只读(公共读走 webaz.xyz);
8
+ * 仅 WEBAZ_MODE=sandbox 才用本机库(与全网隔离,dev/demo)。默认【不是】sandbox。详见 RFC-003。
8
9
  *
9
- * 关联 / Related: AGENTS.md(项目地图) · RFC-003(双模) · RFC-004(webaz_feedback)
10
+ * 关联 / Related: AGENTS.md(项目地图) · RFC-003(三态:network / network_readonly / sandbox) · RFC-004(webaz_feedback)
10
11
  */
11
- import { startMCPServer } from './layer1-agent/L1-1-mcp-server/server.js';
12
- startMCPServer().catch((err) => {
12
+ // ⚠️ 只【静态】import cli.js(纯:version + network-mode,无副作用)。【不】静态 import server.js ——
13
+ // server.js 顶层会 initDatabase()/建 schema,静态 import 会让 --version/--help/--mode 也触发 DB 副作用
14
+ // (写 ~/.webaz/*.db)+ 加载整个 server(#186 审计 P1)。故 server.js 只在真正启动时【动态】import。
15
+ import { cliQuickResponse, runDoctor } from './layer1-agent/L1-1-mcp-server/cli.js';
16
+ async function main() {
17
+ const argv = process.argv.slice(2);
18
+ const quick = cliQuickResponse(argv, process.env); // --version / --help / --mode(纯,不碰 DB/server)
19
+ if (quick !== null) {
20
+ console.log(quick);
21
+ process.exit(0);
22
+ }
23
+ if (argv.includes('--doctor')) {
24
+ console.log(await runDoctor(process.env));
25
+ process.exit(0);
26
+ }
27
+ // 只有真正启动 stdio server 时才动态加载 server.js(此时才允许 DB 初始化等副作用)。
28
+ const { startMCPServer } = await import('./layer1-agent/L1-1-mcp-server/server.js');
29
+ await startMCPServer();
30
+ }
31
+ main().catch((err) => {
13
32
  console.error('MCP Server 启动失败:', err);
14
33
  process.exit(1);
15
34
  });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Merchant Base-Bond (v1 collateral-only) — domain skeleton (PR1, testnet/dev scaffold).
3
+ *
4
+ * 设计真相源:docs/modules/MERCHANT-BASE-BOND-DESIGN.INTERNAL.md(v1 candidate-locked)。
5
+ * 本模块是【纯域逻辑骨架】:状态机 + 资格派生 + 类型。**不写 DB、不接任何 live 路径、不碰资金。**
6
+ *
7
+ * v1 已锁(见设计稿 §12):
8
+ * - 链上非托管锁仓合约持有 collateral;买家货款永不进合约;平台费【不上链】(链下账务)。
9
+ * - slash 仅治理触发 + 链上硬约束(v1 无自动/小额 slash)。
10
+ * - 合约 v1 不可升级;链 = Base + 白名单 USDC(单链);入门门 = 单一 base_bond_min_units(不分档)。
11
+ * - wallet rotation = seller 签名 + WebAZ 授权;治理仅紧急。
12
+ * - 计价用 base_bond_min_units(USDC 整数单位),不引预言机。
13
+ *
14
+ * ⚠️ MERCHANT_BOND_V1_ENABLED 默认 false:整套 v1 在 testnet/dev scaffold 阶段【关闭】,
15
+ * mainnet + 真实 USDC 需先过 法务 + 外部合约审计 + Holden 批准(设计稿 §9)。
16
+ */
17
+ /** 全局开关:v1 默认关闭(PR1 scaffold)。真实启用前永远 false。 */
18
+ export const MERCHANT_BOND_V1_ENABLED = false;
19
+ /** v1 链:Base + 白名单 USDC(单链;仅 chain profile 占位,无多链抽象)。 */
20
+ export const MERCHANT_BOND_CHAIN_PROFILE = {
21
+ key: 'base',
22
+ // 真实 chainId / USDC 地址 / 确认数 N 在 mainnet 接入时由治理/配置定稿(政策参数);此处仅占位。
23
+ chainId: null,
24
+ usdcAddress: null,
25
+ minConfirmations: null,
26
+ };
27
+ /** Seller 生命周期状态(枚举;`paused` 不是 seller 状态,pause 是正交 flags)。 */
28
+ export const BOND_STATUS = {
29
+ NONE: 'none',
30
+ PENDING_CONFIRMATIONS: 'pending_confirmations',
31
+ ACTIVE: 'active',
32
+ COOLING: 'cooling',
33
+ WITHDRAWABLE: 'withdrawable',
34
+ WITHDRAWN: 'withdrawn',
35
+ SLASHED_BELOW_MIN: 'slashed_below_min',
36
+ };
37
+ /** 合法状态转移(域层薄校验;真相仍以链上合约 event 为准)。 */
38
+ const VALID_TRANSITIONS = {
39
+ [BOND_STATUS.NONE]: [BOND_STATUS.PENDING_CONFIRMATIONS],
40
+ [BOND_STATUS.PENDING_CONFIRMATIONS]: [BOND_STATUS.ACTIVE, BOND_STATUS.NONE],
41
+ [BOND_STATUS.ACTIVE]: [BOND_STATUS.COOLING, BOND_STATUS.SLASHED_BELOW_MIN],
42
+ [BOND_STATUS.COOLING]: [BOND_STATUS.WITHDRAWABLE, BOND_STATUS.ACTIVE],
43
+ [BOND_STATUS.WITHDRAWABLE]: [BOND_STATUS.WITHDRAWN, BOND_STATUS.ACTIVE],
44
+ [BOND_STATUS.WITHDRAWN]: [],
45
+ [BOND_STATUS.SLASHED_BELOW_MIN]: [BOND_STATUS.ACTIVE],
46
+ };
47
+ export function canTransitionBond(from, to) {
48
+ return (VALID_TRANSITIONS[from] || []).includes(to);
49
+ }
50
+ /**
51
+ * 资格派生(单一真相函数:readiness 与 create gate 必须都调它,定义逐字一致)。
52
+ * fail-closed:开关关 / 未绑钱包 / 未确认 / 状态非 active / 担保不足 → false。
53
+ * v1 入门门 = 单一 base_bond_min_units(不分档)。
54
+ */
55
+ export function deriveSellerHasProductionBaseBondLocked(view, baseBondMinUnits) {
56
+ if (!MERCHANT_BOND_V1_ENABLED)
57
+ return false; // v1 scaffold 默认关 → 永远不翻真实资格
58
+ if (!view)
59
+ return false;
60
+ return view.walletBound
61
+ && view.confirmed
62
+ && view.status === BOND_STATUS.ACTIVE
63
+ && view.collateralUnits >= baseBondMinUnits;
64
+ }
@@ -0,0 +1,78 @@
1
+ import { toUnits } from './money.js';
2
+ export const EXPOSURE_FACTOR_BPS_PARAM = 'direct_pay.exposure_factor_bps';
3
+ export const EXPOSURE_FACTOR_BPS_DEFAULT = 8000;
4
+ export class ExposureCapConfigError extends Error {
5
+ }
6
+ /** fail-closed 读取 factor:整数 ∈ [1,10000];缺失/非数/越界 → 抛 ExposureCapConfigError。 */
7
+ export function readExposureFactorBps(getProtocolParam) {
8
+ const raw = getProtocolParam(EXPOSURE_FACTOR_BPS_PARAM, undefined);
9
+ const n = Number(raw);
10
+ if (raw === undefined || raw === null || raw === '' || !Number.isFinite(n) || !Number.isInteger(n) || n <= 0 || n > 10000) {
11
+ throw new ExposureCapConfigError(`${EXPOSURE_FACTOR_BPS_PARAM} invalid (got ${JSON.stringify(raw)}); must be integer in [1,10000]`);
12
+ }
13
+ return n;
14
+ }
15
+ /** 卖家当前有效抵押(USDC integer units,big-int 安全):merchant_bond_deposits status='active' 之和。无 → 0n。 */
16
+ export function getActiveCollateralUnits(db, sellerId) {
17
+ let rows = [];
18
+ // 表缺失(旧/最小库)→ 视作无抵押(0n),休眠安全:不让缺表破坏现有直付建单。
19
+ try {
20
+ rows = db.prepare("SELECT collateral_units FROM merchant_bond_deposits WHERE seller_id = ? AND status = 'active'").all(sellerId);
21
+ }
22
+ catch {
23
+ return 0n;
24
+ }
25
+ let sum = 0n;
26
+ for (const r of rows) {
27
+ try {
28
+ sum += BigInt(r.collateral_units || '0');
29
+ }
30
+ catch { /* 坏行跳过,不计入(fail-closed:少算抵押更保守) */ }
31
+ }
32
+ return sum;
33
+ }
34
+ /**
35
+ * 开放敞口状态(未完全关闭的 direct_p2p 单)。终态【不】计入(释放敞口):
36
+ * completed / cancelled / fault_* / resolved_for_seller / refunded_partial / refunded_full / dispute_dismissed / expired。
37
+ * ⚠️ direct_expired_unconfirmed 是【非终态】(超时不静默关单,仍可转 disputed / cancelled,见 transitions.ts)→ 必须计入,
38
+ * 漏算会低估敞口(对风险闸是危险方向)。created 为建单原子前态(direct_p2p 一般即转 window),防御性纳入(过度计入只会更严、无害)。
39
+ */
40
+ export const OPEN_EXPOSURE_STATUSES = [
41
+ 'created', 'direct_pay_window', 'direct_expired_unconfirmed',
42
+ 'accepted', 'shipped', 'picked_up', 'in_transit', 'delivered', 'confirmed', 'disputed',
43
+ ];
44
+ /** 卖家当前 Direct Pay 开放敞口(units):未完全关闭的 direct_p2p 单 total_amount 之和。 */
45
+ export function computeDirectPayOpenExposureUnits(db, sellerId) {
46
+ const placeholders = OPEN_EXPOSURE_STATUSES.map(() => '?').join(',');
47
+ const rows = db.prepare(`SELECT total_amount FROM orders WHERE seller_id = ? AND payment_rail = 'direct_p2p' AND status IN (${placeholders})`).all(sellerId, ...OPEN_EXPOSURE_STATUSES);
48
+ let sum = 0n;
49
+ for (const r of rows)
50
+ sum += BigInt(toUnits(Number(r.total_amount) || 0));
51
+ return sum;
52
+ }
53
+ /** 纯函数:本单是否在敞口上限内。allowed = collateral × bps / 10000;open + new ≤ allowed。 */
54
+ export function withinExposureCap(args) {
55
+ const allowed = args.activeCollateralUnits * BigInt(args.factorBps) / 10000n;
56
+ return args.openExposureUnits + args.newOrderUnits <= allowed;
57
+ }
58
+ /**
59
+ * create-gate 主入口。休眠安全:collateral==0 → ok(N/A,缓交路径由 deferral quota 管,不读 factor)。
60
+ * collateral>0 → 读 factor(fail-closed)+ 比较;超出 → 拒(EXPOSURE_CAP_EXCEEDED)。
61
+ */
62
+ export function enforceCollateralExposureGate(db, sellerId, newOrderUnits, getProtocolParam) {
63
+ const collateral = getActiveCollateralUnits(db, sellerId);
64
+ if (collateral <= 0n)
65
+ return { ok: true }; // 无真实抵押 → 敞口上限不适用(休眠)
66
+ let factorBps;
67
+ try {
68
+ factorBps = readExposureFactorBps(getProtocolParam);
69
+ }
70
+ catch (e) {
71
+ return { ok: false, error_code: 'EXPOSURE_CAP_CONFIG', reason: e.message };
72
+ } // fail-closed
73
+ const openExposureUnits = computeDirectPayOpenExposureUnits(db, sellerId);
74
+ if (!withinExposureCap({ activeCollateralUnits: collateral, openExposureUnits, newOrderUnits: BigInt(newOrderUnits), factorBps })) {
75
+ return { ok: false, error_code: 'EXPOSURE_CAP_EXCEEDED', reason: 'direct-pay open exposure exceeds collateral-backed cap' };
76
+ }
77
+ return { ok: true };
78
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Merchant Base-Bond — chain watcher skeleton (PR1, testnet/dev scaffold).
3
+ *
4
+ * 设计:docs/modules/MERCHANT-BASE-BOND-DESIGN.INTERNAL.md §4.1/§4.2。
5
+ * 职责(未来 testnet 才接真实 RPC):监听合约 deposit/slash/withdraw event → ≥N confirmations 后
6
+ * 更新 DB 镜像(merchant_bond_*)。reorg 后冻结派生资格 + 告警。
7
+ *
8
+ * ⚠️ PR1 阶段:**纯骨架、默认关、不连任何 RPC、不写资金、不接 live 路径。** 启用前需:
9
+ * chain profile 定稿 + 法务 + 外部合约审计 + Holden 批准(设计稿 §9)。
10
+ */
11
+ import { MERCHANT_BOND_V1_ENABLED, MERCHANT_BOND_CHAIN_PROFILE } from './merchant-bond-domain.js';
12
+ /**
13
+ * 启动 watcher。**PR1:若未启用(默认)或 chain profile 未定稿 → 直接返回 no-op,绝不连 RPC。**
14
+ * 真实事件处理(deposit recognition / confirmations / reorg freeze)留待后续 testnet PR。
15
+ */
16
+ export function startMerchantBondWatcher(_deps = {}) {
17
+ const profileReady = MERCHANT_BOND_CHAIN_PROFILE.chainId != null
18
+ && MERCHANT_BOND_CHAIN_PROFILE.usdcAddress != null
19
+ && MERCHANT_BOND_CHAIN_PROFILE.minConfirmations != null;
20
+ if (!MERCHANT_BOND_V1_ENABLED || !profileReady) {
21
+ // disabled scaffold:no RPC、no timer、no DB write。
22
+ return { enabled: false, stop: () => { } };
23
+ }
24
+ // 真实监听实现 = 后续 testnet PR(deposit/slash/withdraw event → ≥N confirmations → DB 镜像;reorg 冻结+告警)。
25
+ throw new Error('MerchantBondWatcher: real chain listening not implemented in PR1 scaffold');
26
+ }
@@ -0,0 +1,29 @@
1
+ import { takeFeeAtCompletion, releaseFeeStake, slashFeeStakeToPenalty } from './direct-pay-ledger.js';
2
+ /**
3
+ * Rail 1 = 非托管场外直付 + 信誉(buyer protection = reputation-only, refund = none)。
4
+ * ⚠️ 本 seam 当前【未接线】(getPaymentRail 无调用方)。Rail1 平台费【实时路径】= 首单宽限 + 预充值续用:
5
+ * 建单走【首单宽限 + 预充值门】(direct-pay-create.ts),完成时 accrue 应收(server.ts settleOrder direct_p2p 分支)。
6
+ * 下方 collectFeeAtCompletion/release/slash 仍指向旧 WAZ fee-stake helper = 遗留/forward-compat 占位;
7
+ * 若将来真接线本 seam,须改为应收/预充值语义(accrue / prepay)。
8
+ */
9
+ export const RAIL_DIRECT_P2P = {
10
+ contract: {
11
+ id: 'direct_p2p',
12
+ buyerProtection: 'reputation_only',
13
+ refund: 'none',
14
+ disputeRemedyBasis: 'evidence_reputation',
15
+ custodialOfTradeFunds: false,
16
+ },
17
+ collectFeeAtCompletion: (db, orderId) => takeFeeAtCompletion(db, { orderId }),
18
+ releaseOnExpiryOrCancel: (db, orderId) => releaseFeeStake(db, { orderId }),
19
+ slashOnFault: (db, orderId, txnId, reason) => slashFeeStakeToPenalty(db, { orderId, txnId, reason }),
20
+ };
21
+ /**
22
+ * 取轨道实现。只 Rail 1 已实现;'escrow' 走既有系统(不经此抽象);'onchain_full_stake' / 'psp' = GATED(未实现)。
23
+ * 故意用显式 switch,不建插件注册框架(anti-YAGNI)。
24
+ */
25
+ export function getPaymentRail(railId) {
26
+ if (railId === 'direct_p2p')
27
+ return RAIL_DIRECT_P2P;
28
+ return null; // escrow=既有系统; onchain_full_stake/psp=未实现(forward-compat 契约见上)
29
+ }