@seasonkoh/webaz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -0
- package/dist/cron-enforcement.js +83 -0
- package/dist/demo-agent.js +167 -0
- package/dist/index.js +182 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +179 -0
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +183 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +197 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +306 -0
- package/dist/layer1-agent/L1-1-mcp-server/auth.js +21 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +1062 -0
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +217 -0
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +678 -0
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +205 -0
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +258 -0
- package/dist/mcp.js +11 -0
- package/dist/pwa/server.js +760 -0
- package/dist/test-dispute.js +153 -0
- package/dist/test-manifest.js +61 -0
- package/dist/test-mcp-tools.js +135 -0
- package/dist/test-reputation.js +116 -0
- package/dist/test-skill-market.js +101 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# WebAZ
|
|
2
|
+
|
|
3
|
+
让 AI Agent 成为去中心化商业协议的原生参与者。卖家零额外工作量接入新渠道,买家通过 Agent 自动购物,人类与 AI 在同一协议上平等参与。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 核心特性
|
|
8
|
+
|
|
9
|
+
- **Agent 原生**:通过 MCP 协议让 Claude 直接搜索商品、下单、确认收货
|
|
10
|
+
- **人类 + Agent 双轨**:PWA 供人类操作,MCP 供 Agent 调用,共用同一后端
|
|
11
|
+
- **自动执法**:每笔交易有明确责任方,超时未履行自动判责,无需人工干预
|
|
12
|
+
- **争议系统**:买卖双方举证,仲裁员裁定,败诉方缴纳 1% 仲裁费
|
|
13
|
+
- **声誉体系**:5 级制(新手→传奇),影响质押折扣和搜索排名
|
|
14
|
+
- **Skill 市场**:卖家发布 auto_accept / catalog_sync 等能力插件,Agent 订阅后自动享用
|
|
15
|
+
- **货币单位**:WAZ(Phase 0 为模拟代币,Phase 2 接入链上稳定币)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
### 方式一:Claude MCP 接入(Agent 原生体验)
|
|
22
|
+
|
|
23
|
+
**1. 克隆并安装**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone <repo-url> webaz
|
|
27
|
+
cd webaz
|
|
28
|
+
npm install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**2. 添加到 Claude Desktop 配置**
|
|
32
|
+
|
|
33
|
+
编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"webaz": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["tsx", "/your/path/to/webaz/src/mcp.ts"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
重启 Claude Desktop。
|
|
47
|
+
|
|
48
|
+
**3. 开始使用**
|
|
49
|
+
|
|
50
|
+
在 Claude 对话里说:
|
|
51
|
+
|
|
52
|
+
> "帮我在 WebAZ 注册一个买家账号,然后搜索一下有什么商品"
|
|
53
|
+
|
|
54
|
+
Claude 会自动调用 `webaz_register` 和 `webaz_search` 完成操作。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 方式二:PWA 浏览器界面
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd webaz
|
|
62
|
+
npm run pwa
|
|
63
|
+
# 打开 http://localhost:3000
|
|
64
|
+
# 手机访问:http://<本机IP>:3000
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
注册账号后即可使用完整功能。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## MCP 工具清单
|
|
72
|
+
|
|
73
|
+
| 工具 | 说明 | 主要参数 |
|
|
74
|
+
|------|------|----------|
|
|
75
|
+
| `webaz_info` | 获取协议概览和实时统计 | — |
|
|
76
|
+
| `webaz_register` | 注册账号,获取 api_key | `name`, `role` |
|
|
77
|
+
| `webaz_search` | 搜索商品(按声誉权重排序) | `query`, `category`, `max_price` |
|
|
78
|
+
| `webaz_list_product` | 卖家上架商品 | `title`, `price`, `stock`, `api_key` |
|
|
79
|
+
| `webaz_place_order` | 买家下单 | `product_id`, `shipping_address`, `api_key` |
|
|
80
|
+
| `webaz_update_order` | 更新订单状态(接单/发货/确认/争议) | `order_id`, `action`, `api_key` |
|
|
81
|
+
| `webaz_get_status` | 查询订单/钱包/争议详情 | `order_id` / `wallet` / `dispute_id`, `api_key` |
|
|
82
|
+
| `webaz_wallet` | 查看钱包余额 | `api_key` |
|
|
83
|
+
| `webaz_notifications` | 查询未读通知 | `api_key` |
|
|
84
|
+
| `webaz_dispute` | 争议操作(查看/举证/裁定) | `action`, `api_key` |
|
|
85
|
+
| `webaz_skill` | Skill 市场(发布/订阅) | `action`, `api_key` |
|
|
86
|
+
|
|
87
|
+
完整协议规范(状态机/经济模型/争议规则)可通过 MCP Resource 读取:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
webaz://protocol/manifest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 角色说明
|
|
96
|
+
|
|
97
|
+
| 角色 | 可以是人类或 Agent | 职责 |
|
|
98
|
+
|------|-------------------|------|
|
|
99
|
+
| `buyer` 买家 | ✅ 两者均可 | 浏览商品、下单、确认收货或发起争议 |
|
|
100
|
+
| `seller` 卖家 | ✅ 两者均可 | 上架商品、接单、发货,可发布 Skill |
|
|
101
|
+
| `logistics` 物流 | ✅ 两者均可 | 揽收、运输、投递,回传快递单号 |
|
|
102
|
+
| `arbitrator` 仲裁员 | ✅ 两者均可 | 审查争议证据、做出裁定 |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 交易流程
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
买家下单(paid)
|
|
110
|
+
→ 卖家接单(accepted) ← 超时 24h:fault_seller
|
|
111
|
+
→ 卖家发货(shipped) ← 超时 72h:fault_seller(需选择物流公司)
|
|
112
|
+
→ 物流揽收(picked_up) ← 超时 48h:fault_logistics(回传快递单号)
|
|
113
|
+
→ 运输中(in_transit)
|
|
114
|
+
→ 投递完成(delivered) ← 超时 48h:fault_logistics
|
|
115
|
+
→ 买家确认(confirmed) ← 超时 72h:自动确认
|
|
116
|
+
→ 完成结算(completed)
|
|
117
|
+
|
|
118
|
+
买家在 delivered 阶段可发起争议:
|
|
119
|
+
→ 被告 48h 内举证 → 仲裁员 120h 内裁定
|
|
120
|
+
→ 超时不回应:自动判发起方胜诉
|
|
121
|
+
→ 败诉方缴纳订单金额 1% 仲裁费(最低 1 WAZ)
|
|
122
|
+
→ 裁定结果:全额退款 / 释放给卖家 / 部分退款 / 责任分配
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 资金分配
|
|
128
|
+
|
|
129
|
+
每笔成交按以下比例自动分配:
|
|
130
|
+
|
|
131
|
+
| 接收方 | 比例 |
|
|
132
|
+
|--------|------|
|
|
133
|
+
| 卖家 | ~93%(扣除各项费用后) |
|
|
134
|
+
| 物流方 | 5% |
|
|
135
|
+
| 协议费 | 2% |
|
|
136
|
+
| 推荐人(如有) | 3% |
|
|
137
|
+
|
|
138
|
+
卖家上架需质押 15% 作为保证金(声誉越高折扣越大,最低 5%)。
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 开发命令
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run pwa # 启动 WebAZ 服务(含自动执法,端口 3000)
|
|
146
|
+
npm run mcp # 单独启动 MCP Server(供 Claude Desktop 调用)
|
|
147
|
+
npm run demo # 跑完整交易演示脚本
|
|
148
|
+
npm run test-dispute # 测试争议系统(三场景)
|
|
149
|
+
npm run test-skill # 测试 Skill 市场
|
|
150
|
+
npm run test-rep # 测试声誉系统
|
|
151
|
+
npm run test-manifest# 测试协议 Manifest
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 技术栈
|
|
157
|
+
|
|
158
|
+
| 方向 | 选择 |
|
|
159
|
+
|------|------|
|
|
160
|
+
| 运行时 | Node.js + TypeScript |
|
|
161
|
+
| Agent 接口 | MCP (Model Context Protocol) |
|
|
162
|
+
| 数据库 | SQLite(Phase 0),PostgreSQL(Phase 1+) |
|
|
163
|
+
| 前端 | PWA — 手机浏览器直接访问,无需安装 |
|
|
164
|
+
| 链(Phase 2) | 待定:Base / Optimism |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 当前阶段
|
|
169
|
+
|
|
170
|
+
**Phase 0 · 概念验证** ✅ 完成
|
|
171
|
+
**Phase 1 · 功能完善** ✅ 完成
|
|
172
|
+
|
|
173
|
+
Phase 2 将把核心资金和状态上链,实现真正的去中心化。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 路线图
|
|
178
|
+
|
|
179
|
+
- [x] 状态机 + 责任归因引擎
|
|
180
|
+
- [x] MCP Server(11 个工具)
|
|
181
|
+
- [x] 通知系统(SSE 实时推送)
|
|
182
|
+
- [x] 争议系统(举证 + 超时自动裁定 + 仲裁费)
|
|
183
|
+
- [x] 声誉积分体系
|
|
184
|
+
- [x] Skill 市场
|
|
185
|
+
- [x] Protocol Manifest(机器可读协议规范)
|
|
186
|
+
- [x] PWA 前端(全角色覆盖,人类 + Agent 双轨)
|
|
187
|
+
- [ ] 链上集成(Base/Optimism)
|
|
188
|
+
- [ ] IPFS 证据存储
|
|
189
|
+
- [ ] 治理 DAO
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DCP 协议自动执法进程
|
|
3
|
+
*
|
|
4
|
+
* 每 5 分钟扫描一次:
|
|
5
|
+
* 1. 订单超时判责(checkTimeouts)
|
|
6
|
+
* 2. 争议超时自动裁定(checkDisputeTimeouts)
|
|
7
|
+
*
|
|
8
|
+
* 运行方式:npm run enforcement
|
|
9
|
+
* 生产环境建议用 pm2 或 systemd 守护进程保活
|
|
10
|
+
*/
|
|
11
|
+
import { initDatabase } from './layer0-foundation/L0-1-database/schema.js';
|
|
12
|
+
import { initSystemUser, checkTimeouts } from './layer0-foundation/L0-2-state-machine/engine.js';
|
|
13
|
+
import { initDisputeSchema, checkDisputeTimeouts } from './layer3-trust/L3-1-dispute-engine/dispute-engine.js';
|
|
14
|
+
import { initReputationSchema, recordViolationReputation, recordDisputeReputation } from './layer4-economics/L4-3-reputation/reputation-engine.js';
|
|
15
|
+
const INTERVAL_MS = 5 * 60 * 1000; // 5 分钟
|
|
16
|
+
const db = initDatabase();
|
|
17
|
+
initSystemUser(db);
|
|
18
|
+
initDisputeSchema(db);
|
|
19
|
+
initReputationSchema(db);
|
|
20
|
+
function timestamp() {
|
|
21
|
+
return new Date().toLocaleString('zh-CN', { hour12: false });
|
|
22
|
+
}
|
|
23
|
+
function line() { console.log('─'.repeat(55)); }
|
|
24
|
+
async function enforce() {
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
// ── 1. 订单超时判责 ───────────────────────────────────────
|
|
27
|
+
const orderResult = checkTimeouts(db);
|
|
28
|
+
// ── 2. 争议超时自动裁定 ───────────────────────────────────
|
|
29
|
+
const disputeResult = checkDisputeTimeouts(db);
|
|
30
|
+
const elapsed = Date.now() - start;
|
|
31
|
+
const totalActions = orderResult.processed + disputeResult.processed;
|
|
32
|
+
if (totalActions > 0) {
|
|
33
|
+
line();
|
|
34
|
+
console.log(`⚡ [${timestamp()}] 执法扫描 完成 (${elapsed}ms)`);
|
|
35
|
+
if (orderResult.processed > 0) {
|
|
36
|
+
console.log(`\n 📦 订单超时判责 × ${orderResult.processed}`);
|
|
37
|
+
orderResult.details.forEach(d => {
|
|
38
|
+
console.log(` ${d.orderId} ${d.action}`);
|
|
39
|
+
// 判责的终态:fault_seller / fault_logistics / fault_buyer
|
|
40
|
+
const faultMatch = d.action.match(/→ (fault_\w+)/);
|
|
41
|
+
if (faultMatch)
|
|
42
|
+
recordViolationReputation(db, d.orderId, faultMatch[1]);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (disputeResult.processed > 0) {
|
|
46
|
+
console.log(`\n ⚖️ 争议自动裁定 × ${disputeResult.processed}`);
|
|
47
|
+
disputeResult.details.forEach(d => {
|
|
48
|
+
console.log(` ${d.disputeId} ${d.action}`);
|
|
49
|
+
if (d.winnerId && d.loserId && d.orderId) {
|
|
50
|
+
recordDisputeReputation(db, d.orderId, d.winnerId, d.loserId);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
line();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// 无动作时只打印一行心跳,保持日志整洁
|
|
58
|
+
process.stdout.write(`\r⏱ [${timestamp()}] 扫描完成,无超时事件`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ── 主循环 ────────────────────────────────────────────────────
|
|
62
|
+
console.log('\n🦞 DCP Protocol — 自动执法进程启动');
|
|
63
|
+
console.log(` 扫描间隔:${INTERVAL_MS / 1000}s`);
|
|
64
|
+
console.log(` 职责:订单超时判责 + 争议超时自动裁定`);
|
|
65
|
+
line();
|
|
66
|
+
// 启动时立即执行一次
|
|
67
|
+
enforce().catch(console.error);
|
|
68
|
+
// 定期执行
|
|
69
|
+
const timer = setInterval(() => {
|
|
70
|
+
enforce().catch(err => {
|
|
71
|
+
console.error(`\n❌ [${timestamp()}] 执法扫描出错:`, err.message);
|
|
72
|
+
});
|
|
73
|
+
}, INTERVAL_MS);
|
|
74
|
+
// 优雅退出
|
|
75
|
+
process.on('SIGINT', () => {
|
|
76
|
+
clearInterval(timer);
|
|
77
|
+
console.log(`\n\n⏹ 执法进程已停止`);
|
|
78
|
+
process.exit(0);
|
|
79
|
+
});
|
|
80
|
+
process.on('SIGTERM', () => {
|
|
81
|
+
clearInterval(timer);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DCP Agent 交互演示
|
|
3
|
+
* 模拟两个 Agent(卖家Agent + 买家Agent)通过 MCP 工具完成一笔真实交易
|
|
4
|
+
* 这就是真实 Agent 调用时会发生的事情
|
|
5
|
+
*/
|
|
6
|
+
import { initDatabase, generateId } from './layer0-foundation/L0-1-database/schema.js';
|
|
7
|
+
import { initSystemUser, transition, getOrderStatus } from './layer0-foundation/L0-2-state-machine/engine.js';
|
|
8
|
+
const db = initDatabase();
|
|
9
|
+
initSystemUser(db);
|
|
10
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
11
|
+
function line() { console.log('─'.repeat(65)); }
|
|
12
|
+
function addHours(date, hours) {
|
|
13
|
+
return new Date(date.getTime() + hours * 3_600_000).toISOString();
|
|
14
|
+
}
|
|
15
|
+
// 模拟 Agent 说话的感觉
|
|
16
|
+
async function agentSay(role, emoji, msg) {
|
|
17
|
+
await sleep(120);
|
|
18
|
+
console.log(`\n${emoji} [${role} Agent]`);
|
|
19
|
+
console.log(` ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
async function toolCall(name, result) {
|
|
22
|
+
await sleep(80);
|
|
23
|
+
console.log(` → 调用工具 ${name}`);
|
|
24
|
+
console.log(` ← ${JSON.stringify(result, null, 0).slice(0, 120)}...`);
|
|
25
|
+
}
|
|
26
|
+
async function main() {
|
|
27
|
+
console.clear();
|
|
28
|
+
console.log('🦞 DCP Protocol — Agent 交互演示');
|
|
29
|
+
console.log(' 两个 Agent 自动完成一笔真实去中心化交易');
|
|
30
|
+
line();
|
|
31
|
+
// ── STEP 1: 卖家 Agent 注册 ───────────────────────────────────
|
|
32
|
+
await agentSay('卖家', '🏪', '我要在 DCP 协议上开店,先注册一个卖家账号');
|
|
33
|
+
const sellerId = generateId('usr');
|
|
34
|
+
const sellerKey = generateId('key');
|
|
35
|
+
db.prepare('INSERT INTO users (id, name, role, api_key) VALUES (?, ?, ?, ?)').run(sellerId, '竹韵手工坊', 'seller', sellerKey);
|
|
36
|
+
db.prepare('INSERT INTO wallets (user_id, balance) VALUES (?, 500)').run(sellerId);
|
|
37
|
+
await toolCall('dcp_register', { name: '竹韵手工坊', role: 'seller' });
|
|
38
|
+
console.log(` ✅ 注册成功!api_key 已保存`);
|
|
39
|
+
// ── STEP 2: 卖家 Agent 上架商品 ────────────────────────────────
|
|
40
|
+
await agentSay('卖家', '🏪', '把我的拳头产品上架,定价 168 DCP,自动质押 15% 保证金');
|
|
41
|
+
const productId = generateId('prd');
|
|
42
|
+
const price = 168;
|
|
43
|
+
const stake = Math.round(price * 0.15 * 100) / 100;
|
|
44
|
+
db.prepare('INSERT INTO products (id, seller_id, title, description, price, stock, category, stake_amount) VALUES (?,?,?,?,?,?,?,?)')
|
|
45
|
+
.run(productId, sellerId, '竹编茶叶罐(250g装)', '云南手工竹编,密封保鲜,适合储存普洱、岩茶', price, 10, '茶具', stake);
|
|
46
|
+
db.prepare('UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ?').run(stake, stake, sellerId);
|
|
47
|
+
await toolCall('dcp_list_product', { title: '竹编茶叶罐', price: 168, stock: 10 });
|
|
48
|
+
console.log(` ✅ 商品已上架!质押 ${stake} DCP,买家现在可以搜索到`);
|
|
49
|
+
line();
|
|
50
|
+
// ── STEP 3: 买家 Agent 注册 ────────────────────────────────────
|
|
51
|
+
await agentSay('买家', '🛒', '帮我注册买家身份,我想买点好茶具');
|
|
52
|
+
const buyerId = generateId('usr');
|
|
53
|
+
const buyerKey = generateId('key');
|
|
54
|
+
db.prepare('INSERT INTO users (id, name, role, api_key) VALUES (?, ?, ?, ?)').run(buyerId, '陈先生', 'buyer', buyerKey);
|
|
55
|
+
db.prepare('INSERT INTO wallets (user_id, balance) VALUES (?, 1000)').run(buyerId);
|
|
56
|
+
await toolCall('dcp_register', { name: '陈先生', role: 'buyer' });
|
|
57
|
+
console.log(` ✅ 注册成功!初始余额 1000 DCP`);
|
|
58
|
+
// ── STEP 4: 买家 Agent 搜索 ────────────────────────────────────
|
|
59
|
+
await agentSay('买家', '🛒', '帮我搜索一下茶具,预算 200 以内');
|
|
60
|
+
const results = db.prepare(`
|
|
61
|
+
SELECT p.*, u.name as seller_name FROM products p
|
|
62
|
+
JOIN users u ON p.seller_id = u.id
|
|
63
|
+
WHERE p.status = 'active' AND (p.title LIKE '%茶%' OR p.category = '茶具') AND p.price <= 200
|
|
64
|
+
`).all();
|
|
65
|
+
await toolCall('dcp_search', { query: '茶具', max_price: 200 });
|
|
66
|
+
console.log(` ← 找到 ${results.length} 件商品:`);
|
|
67
|
+
results.forEach(p => console.log(` · ${p.title} ¥${p.price} DCP 卖家:${p.seller_name}`));
|
|
68
|
+
// ── STEP 5: 买家 Agent 下单 ────────────────────────────────────
|
|
69
|
+
await agentSay('买家', '🛒', `好!买这个竹编茶叶罐,地址是上海市静安区南京西路×号`);
|
|
70
|
+
const orderId = generateId('ord');
|
|
71
|
+
const now = new Date();
|
|
72
|
+
db.prepare(`
|
|
73
|
+
INSERT INTO orders (id, product_id, buyer_id, seller_id,
|
|
74
|
+
quantity, unit_price, total_amount, escrow_amount, status,
|
|
75
|
+
shipping_address, pay_deadline, accept_deadline, ship_deadline,
|
|
76
|
+
pickup_deadline, delivery_deadline, confirm_deadline)
|
|
77
|
+
VALUES (?, ?, ?, ?, 1, ?, ?, ?, 'created', '上海市静安区南京西路×号', ?, ?, ?, ?, ?, ?)
|
|
78
|
+
`).run(orderId, productId, buyerId, sellerId, price, price, price, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408));
|
|
79
|
+
db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ?').run(price, price, buyerId);
|
|
80
|
+
db.prepare('UPDATE products SET stock = stock - 1 WHERE id = ?').run(productId);
|
|
81
|
+
transition(db, orderId, 'paid', buyerId, [], '买家付款,资金托管');
|
|
82
|
+
await toolCall('dcp_place_order', { product_id: productId, shipping_address: '上海市...' });
|
|
83
|
+
console.log(` ✅ 订单创建!${price} DCP 已托管`);
|
|
84
|
+
console.log(` ℹ️ 卖家需在 24h 内接单,否则自动退款`);
|
|
85
|
+
line();
|
|
86
|
+
// ── STEP 6: 卖家 Agent 收到通知,接单 ─────────────────────────
|
|
87
|
+
await agentSay('卖家', '🏪', '收到新订单通知!确认接单,3天内安排发货');
|
|
88
|
+
transition(db, orderId, 'accepted', sellerId, [], '确认接单,备货中');
|
|
89
|
+
await toolCall('dcp_update_order', { action: 'accept', order_id: orderId });
|
|
90
|
+
console.log(` ✅ 接单成功!协议记录:卖家已承诺履约`);
|
|
91
|
+
// ── STEP 7: 卖家 Agent 发货 ────────────────────────────────────
|
|
92
|
+
await agentSay('卖家', '🏪', '商品已打包,交给顺丰,单号 SF1234567890,上传发货凭证');
|
|
93
|
+
const evtShip = generateId('evt');
|
|
94
|
+
db.prepare('INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash) VALUES (?,?,?,?,?,?)')
|
|
95
|
+
.run(evtShip, orderId, sellerId, 'photo', '包裹照片+快递单SF1234567890', 'sha256_ship_abc');
|
|
96
|
+
transition(db, orderId, 'shipped', sellerId, [evtShip], '顺丰SF1234567890');
|
|
97
|
+
await toolCall('dcp_update_order', { action: 'ship', evidence_description: '顺丰单号+包裹照片' });
|
|
98
|
+
console.log(` ✅ 发货证明已上链!哈希:sha256_ship_abc`);
|
|
99
|
+
// ── STEP 8: 物流 Agent 揽收 ────────────────────────────────────
|
|
100
|
+
const logisticsId = generateId('usr');
|
|
101
|
+
db.prepare('INSERT INTO users (id, name, role, api_key) VALUES (?,?,?,?)').run(logisticsId, '顺丰小哥李明', 'logistics', generateId('key'));
|
|
102
|
+
db.prepare('INSERT INTO wallets (user_id, balance) VALUES (?, 0)').run(logisticsId);
|
|
103
|
+
db.prepare('UPDATE orders SET logistics_id = ? WHERE id = ?').run(logisticsId, orderId);
|
|
104
|
+
await agentSay('物流', '🚚', '已揽收包裹,上传GPS坐标和扫描记录');
|
|
105
|
+
const evtPickup = generateId('evt');
|
|
106
|
+
db.prepare('INSERT INTO evidence (id, order_id, uploader_id, type, description, metadata) VALUES (?,?,?,?,?,?)')
|
|
107
|
+
.run(evtPickup, orderId, logisticsId, 'gps', '揽收扫描确认', '{"lat":31.22,"lng":121.48,"timestamp":"2026-05-11T10:30:00Z"}');
|
|
108
|
+
transition(db, orderId, 'picked_up', logisticsId, [evtPickup], '包裹完好,已揽收');
|
|
109
|
+
transition(db, orderId, 'in_transit', logisticsId, [], '已发往上海转运中心');
|
|
110
|
+
await toolCall('dcp_update_order', { action: 'pickup', evidence_description: 'GPS+扫描记录' });
|
|
111
|
+
console.log(` ✅ 揽收证明已记录,开始运输`);
|
|
112
|
+
// ── STEP 9: 物流 Agent 投递 ────────────────────────────────────
|
|
113
|
+
await agentSay('物流', '🚚', '已送达,拍门口照片,收件人已签收');
|
|
114
|
+
const evtDeliver = generateId('evt');
|
|
115
|
+
db.prepare('INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash) VALUES (?,?,?,?,?,?)')
|
|
116
|
+
.run(evtDeliver, orderId, logisticsId, 'photo', '投递照片(含门牌)+签收记录', 'sha256_deliver_xyz');
|
|
117
|
+
transition(db, orderId, 'delivered', logisticsId, [evtDeliver], '本人签收,已完成投递');
|
|
118
|
+
await toolCall('dcp_update_order', { action: 'deliver', evidence_description: '投递照片+签收' });
|
|
119
|
+
console.log(` ✅ 投递证明已记录!买家 72h 内确认,否则自动确认`);
|
|
120
|
+
line();
|
|
121
|
+
// ── STEP 10: 买家 Agent 确认 ───────────────────────────────────
|
|
122
|
+
await agentSay('买家', '🛒', '收到了!茶叶罐做工很好,确认收货');
|
|
123
|
+
transition(db, orderId, 'confirmed', buyerId, [], '商品完好,非常满意!');
|
|
124
|
+
// 自动结算
|
|
125
|
+
const sysUser = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
|
|
126
|
+
transition(db, orderId, 'completed', sysUser.id, [], '系统自动结算');
|
|
127
|
+
const protocolFee = Math.round(price * 0.02 * 100) / 100;
|
|
128
|
+
const logisticsFee = Math.round(price * 0.05 * 100) / 100;
|
|
129
|
+
const sellerAmount = price - protocolFee - logisticsFee;
|
|
130
|
+
db.prepare('UPDATE wallets SET escrowed = escrowed - ? WHERE user_id = ?').run(price, buyerId);
|
|
131
|
+
db.prepare('UPDATE wallets SET balance = balance + ?, earned = earned + ? WHERE user_id = ?').run(sellerAmount, sellerAmount, sellerId);
|
|
132
|
+
db.prepare('UPDATE wallets SET balance = balance + ?, earned = earned + ? WHERE user_id = ?').run(logisticsFee, logisticsFee, logisticsId);
|
|
133
|
+
db.prepare('UPDATE wallets SET staked = staked - ?, balance = balance + ? WHERE user_id = ?').run(stake, stake, sellerId);
|
|
134
|
+
await toolCall('dcp_update_order', { action: 'confirm' });
|
|
135
|
+
console.log(` ✅ 确认收货!资金自动结算中...`);
|
|
136
|
+
// ── 最终报告 ────────────────────────────────────────────────────
|
|
137
|
+
line();
|
|
138
|
+
await agentSay('协议系统', '🦞', '交易完成!以下是完整结算报告');
|
|
139
|
+
const statusInfo = getOrderStatus(db, orderId);
|
|
140
|
+
console.log(`\n 📋 订单 ${orderId}`);
|
|
141
|
+
console.log(` 状态:${statusInfo.order.status}(已完成)\n`);
|
|
142
|
+
console.log(` 💰 资金分配(总额 ${price} DCP):`);
|
|
143
|
+
const sellerW = db.prepare('SELECT * FROM wallets WHERE user_id = ?').get(sellerId);
|
|
144
|
+
const logW = db.prepare('SELECT * FROM wallets WHERE user_id = ?').get(logisticsId);
|
|
145
|
+
const buyerW = db.prepare('SELECT * FROM wallets WHERE user_id = ?').get(buyerId);
|
|
146
|
+
console.log(` 卖家(竹韵手工坊):+${sellerAmount} DCP (${((sellerAmount / price) * 100).toFixed(0)}%) 总余额:${sellerW.balance.toFixed(2)}`);
|
|
147
|
+
console.log(` 物流(顺丰小哥): +${logisticsFee} DCP (5%) 总余额:${logW.balance.toFixed(2)}`);
|
|
148
|
+
console.log(` 协议费: -${protocolFee} DCP (2%)`);
|
|
149
|
+
console.log(` 买家(陈先生): -${price} DCP 总余额:${buyerW.balance.toFixed(2)}`);
|
|
150
|
+
console.log(`\n 📜 完整状态历史(每步都有链上记录):`);
|
|
151
|
+
for (const h of statusInfo.history) {
|
|
152
|
+
const evtCount = JSON.parse(h.evidence_ids || '[]').length;
|
|
153
|
+
const evtTag = evtCount > 0 ? ` 📎 ${evtCount}份证据` : '';
|
|
154
|
+
console.log(` ${String(h.from_status).padEnd(12)} → ${String(h.to_status).padEnd(12)} ${String(h.actor_name)}${evtTag}`);
|
|
155
|
+
}
|
|
156
|
+
line();
|
|
157
|
+
console.log('\n🎉 这就是 DCP 协议的第一笔真实交易!');
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(' 用户做了什么:告诉 Agent「买这个」「确认收货」');
|
|
160
|
+
console.log(' Agent 做了什么:处理所有协议交互、证据上传、状态流转');
|
|
161
|
+
console.log(' 协议做了什么:自动托管资金、验证每步合法性、自动结算分成');
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(' 如果任何一方违约(超时/货不对版):');
|
|
164
|
+
console.log(' 协议自动判责 → 自动执行处置 → 不需要任何人工干预');
|
|
165
|
+
line();
|
|
166
|
+
}
|
|
167
|
+
main().catch(console.error);
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DCP 启动入口 & 模块测试
|
|
3
|
+
*/
|
|
4
|
+
import { initDatabase, generateId } from './layer0-foundation/L0-1-database/schema.js';
|
|
5
|
+
import { transition, checkTimeouts, getOrderStatus, initSystemUser } from './layer0-foundation/L0-2-state-machine/engine.js';
|
|
6
|
+
console.log('🦞 DCP — Decentralized Commerce Protocol');
|
|
7
|
+
console.log('─'.repeat(50));
|
|
8
|
+
const db = initDatabase();
|
|
9
|
+
const sysUser = initSystemUser(db); // 确保系统用户存在
|
|
10
|
+
// ─── 测试 L0-2:完整交易状态机流转 ───────────────────────────
|
|
11
|
+
console.log('\n📋 测试 L0-2 状态机引擎\n');
|
|
12
|
+
// 1. 创建测试角色
|
|
13
|
+
const buyer = createUser(db, '张三', 'buyer', 500);
|
|
14
|
+
const seller = createUser(db, '李四店铺', 'seller', 200);
|
|
15
|
+
const logistic = createUser(db, '顺丰速运', 'logistics', 300);
|
|
16
|
+
// 2. 上架商品
|
|
17
|
+
const productId = generateId('prd');
|
|
18
|
+
db.prepare(`
|
|
19
|
+
INSERT INTO products (id, seller_id, title, description, price, stake_amount)
|
|
20
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
21
|
+
`).run(productId, seller.id, '手工皮质钱包', '意大利头层牛皮,手工缝制', 288, 50);
|
|
22
|
+
// 3. 创建订单
|
|
23
|
+
const orderId = generateId('ord');
|
|
24
|
+
const now = new Date();
|
|
25
|
+
db.prepare(`
|
|
26
|
+
INSERT INTO orders (
|
|
27
|
+
id, product_id, buyer_id, seller_id,
|
|
28
|
+
quantity, unit_price, total_amount, escrow_amount,
|
|
29
|
+
status,
|
|
30
|
+
pay_deadline, accept_deadline, ship_deadline,
|
|
31
|
+
pickup_deadline, delivery_deadline, confirm_deadline
|
|
32
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
33
|
+
`).run(orderId, productId, buyer.id, seller.id, 1, 288, 288, 288, 'created', addHours(now, 24), // 24h 内付款
|
|
34
|
+
addHours(now, 48), // 付款后 24h 内接单
|
|
35
|
+
addHours(now, 120), // 接单后 72h 内发货
|
|
36
|
+
addHours(now, 168), // 发货后 48h 内揽收
|
|
37
|
+
addHours(now, 336), // 揽收后 7天内投递
|
|
38
|
+
addHours(now, 408));
|
|
39
|
+
console.log(`✅ 订单创建:${orderId}`);
|
|
40
|
+
console.log(` 商品:手工皮质钱包 ¥288`);
|
|
41
|
+
console.log(` 买家:${buyer.name} → 卖家:${seller.name}\n`);
|
|
42
|
+
// ─── 跑一遍完整的正常流程 ──────────────────────────────────────
|
|
43
|
+
const steps = [
|
|
44
|
+
{
|
|
45
|
+
desc: '💳 买家付款',
|
|
46
|
+
fn: () => transition(db, orderId, 'paid', buyer.id, [], '微信支付 ¥288')
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
desc: '✅ 卖家接单',
|
|
50
|
+
fn: () => transition(db, orderId, 'accepted', seller.id, [], '确认接单,3天内发货')
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
desc: '📦 卖家发货(需要证据)',
|
|
54
|
+
fn: () => {
|
|
55
|
+
// 先创建一条证据记录
|
|
56
|
+
const evidenceId = generateId('evt');
|
|
57
|
+
db.prepare(`
|
|
58
|
+
INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
60
|
+
`).run(evidenceId, orderId, seller.id, 'photo', '包裹外观照片+快递单', 'sha256_mock_abc123');
|
|
61
|
+
return transition(db, orderId, 'shipped', seller.id, [evidenceId], '顺丰 SF1234567890');
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
desc: '🚚 物流揽收(需要证据)',
|
|
66
|
+
fn: () => {
|
|
67
|
+
const evidenceId = generateId('evt');
|
|
68
|
+
db.prepare(`
|
|
69
|
+
INSERT INTO evidence (id, order_id, uploader_id, type, description, metadata)
|
|
70
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
71
|
+
`).run(evidenceId, orderId, logistic.id, 'gps', '揽收GPS坐标', '{"lat":31.23,"lng":121.47}');
|
|
72
|
+
return transition(db, orderId, 'picked_up', logistic.id, [evidenceId], '已揽收,包裹完好');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
desc: '🚛 开始运输',
|
|
77
|
+
fn: () => transition(db, orderId, 'in_transit', logistic.id, [], '已发往上海转运中心')
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
desc: '📬 物流投递(需要证据)',
|
|
81
|
+
fn: () => {
|
|
82
|
+
const evidenceId = generateId('evt');
|
|
83
|
+
db.prepare(`
|
|
84
|
+
INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
86
|
+
`).run(evidenceId, orderId, logistic.id, 'photo', '投递照片(含门牌号)', 'sha256_delivery_xyz789');
|
|
87
|
+
return transition(db, orderId, 'delivered', logistic.id, [evidenceId], '已放前台,收件人已签收');
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
desc: '✅ 买家确认收货',
|
|
92
|
+
fn: () => transition(db, orderId, 'confirmed', buyer.id, [], '商品完好,非常满意')
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
desc: '💰 系统自动结算',
|
|
96
|
+
fn: () => transition(db, orderId, 'completed', sysUser.id, [], '交易完成,资金自动分配')
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
let allPassed = true;
|
|
100
|
+
for (const step of steps) {
|
|
101
|
+
const result = step.fn();
|
|
102
|
+
if (result.success) {
|
|
103
|
+
console.log(` ${step.desc}`);
|
|
104
|
+
console.log(` 状态 → ${result.newStatus} [hist: ${result.historyId}]`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log(` ❌ ${step.desc} 失败:${result.error}`);
|
|
108
|
+
allPassed = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ─── 测试「拒绝非法操作」──────────────────────────────────────
|
|
112
|
+
console.log('\n🚫 测试非法操作(应该全部被拒绝)\n');
|
|
113
|
+
const illegalTests = [
|
|
114
|
+
{
|
|
115
|
+
desc: '物流方尝试取消买家的订单',
|
|
116
|
+
fn: () => transition(db, orderId, 'cancelled', logistic.id)
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
desc: '买家尝试直接跳到 completed',
|
|
120
|
+
fn: () => transition(db, orderId, 'completed', buyer.id)
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
desc: '卖家尝试把 completed 改回 paid',
|
|
124
|
+
fn: () => transition(db, orderId, 'paid', seller.id)
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
for (const test of illegalTests) {
|
|
128
|
+
const result = test.fn();
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
console.log(` ✅ 正确拒绝:${test.desc}`);
|
|
131
|
+
console.log(` 原因:${result.error}`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(` ❌ 漏洞!非法操作被允许了:${test.desc}`);
|
|
135
|
+
allPassed = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ─── 查看完整状态历史 ─────────────────────────────────────────
|
|
139
|
+
console.log('\n📜 订单完整状态历史\n');
|
|
140
|
+
const status = getOrderStatus(db, orderId);
|
|
141
|
+
for (const h of status.history) {
|
|
142
|
+
console.log(` ${h.from_status.padEnd(12)} → ${h.to_status.padEnd(12)} | ${h.actor_name}(${h.actor_role_name})`);
|
|
143
|
+
}
|
|
144
|
+
// ─── 测试超时判责 ─────────────────────────────────────────────
|
|
145
|
+
console.log('\n⏰ 测试超时自动判责\n');
|
|
146
|
+
const orderId2 = generateId('ord');
|
|
147
|
+
db.prepare(`
|
|
148
|
+
INSERT INTO orders (
|
|
149
|
+
id, product_id, buyer_id, seller_id,
|
|
150
|
+
quantity, unit_price, total_amount, escrow_amount, status,
|
|
151
|
+
pay_deadline, accept_deadline, ship_deadline, pickup_deadline, delivery_deadline, confirm_deadline
|
|
152
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
153
|
+
`).run(orderId2, productId, buyer.id, seller.id, 1, 288, 288, 288, 'paid', addHours(now, -48), // 所有截止时间都已过
|
|
154
|
+
addHours(now, -24), // 卖家接单截止时间已过!
|
|
155
|
+
addHours(now, -1), addHours(now, -1), addHours(now, -1), addHours(now, -1));
|
|
156
|
+
console.log(` 模拟场景:卖家超时未接单(截止时间已过 24 小时)`);
|
|
157
|
+
const timeoutResult = checkTimeouts(db);
|
|
158
|
+
console.log(` 处理了 ${timeoutResult.processed} 个超时订单`);
|
|
159
|
+
for (const d of timeoutResult.details) {
|
|
160
|
+
console.log(` ✅ ${d.orderId}:${d.action}`);
|
|
161
|
+
}
|
|
162
|
+
// ─── 最终结论 ─────────────────────────────────────────────────
|
|
163
|
+
console.log('\n' + '─'.repeat(50));
|
|
164
|
+
if (allPassed) {
|
|
165
|
+
console.log('✅ L0-1 数据库:正常');
|
|
166
|
+
console.log('✅ L0-2 状态机:正常(完整流程 + 非法拦截 + 超时判责)');
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.log('❌ 部分测试未通过,请检查上方输出');
|
|
170
|
+
}
|
|
171
|
+
console.log('─'.repeat(50));
|
|
172
|
+
// ─── 工具函数 ─────────────────────────────────────────────────
|
|
173
|
+
function createUser(db, name, role, balance) {
|
|
174
|
+
const id = generateId('usr');
|
|
175
|
+
const apiKey = generateId('key');
|
|
176
|
+
db.prepare(`INSERT INTO users (id, name, role, api_key) VALUES (?, ?, ?, ?)`).run(id, name, role, apiKey);
|
|
177
|
+
db.prepare(`INSERT INTO wallets (user_id, balance) VALUES (?, ?)`).run(id, balance);
|
|
178
|
+
return { id, name, role, apiKey };
|
|
179
|
+
}
|
|
180
|
+
function addHours(date, hours) {
|
|
181
|
+
return new Date(date.getTime() + hours * 3_600_000).toISOString();
|
|
182
|
+
}
|