@wowok/skills 1.1.12 → 1.2.1
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/LICENSE +1 -2
- package/examples/Insurance/Insurance.md +426 -70
- package/examples/MyShop/MyShop.md +349 -228
- package/examples/MyShop/myshop_machine_nodes.json +14 -8
- package/examples/MyShop_Advanced/MyShop_Advanced.md +472 -105
- package/examples/ThreeBody_Signature/ThreeBody_Signature.md +623 -121
- package/examples/Travel/Travel.md +298 -77
- package/package.json +1 -1
- package/wowok-guard/SKILL.md +317 -20
- package/wowok-machine/SKILL.md +72 -0
- package/wowok-order/APPENDIX.md +33 -33
- package/wowok-provider/SKILL.md +26 -12
- package/wowok-scenario/SKILL.md +10 -0
- package/wowok-tools/SKILL.md +2 -2
|
@@ -4,6 +4,24 @@ A complete example demonstrating how to create a service for book signing by the
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## ⚠️ Running Principle
|
|
8
|
+
|
|
9
|
+
> **Run the example in full every time (repeatable).** This example uses `replaceExistName: true` on all object creations — each run generates new objects with new addresses. If you skip build steps, operations may silently act on orphaned objects from previous runs, producing incorrect results. Old objects' configurations do not reflect the current document version.
|
|
10
|
+
|
|
11
|
+
- **Execution order**: Run all build/setup steps (including account generation) in sequence before testing any customer order flow. Do not skip steps — each depends on objects created by prior steps.
|
|
12
|
+
- **Prerequisites**: `three_body_author` and `three_body_customer` with sufficient WOW for gas (≥ 1000 WOW recommended). All on-chain operations require `env.confirmed: true`.
|
|
13
|
+
|
|
14
|
+
### 🔐 Two-Step Confirmation Flow (Production Safety)
|
|
15
|
+
|
|
16
|
+
This example sets `env.confirmed: true` on irreversible operations (e.g., `publish: true` on Machine) for brevity. In real deployments, follow the two-step flow enforced by the ConfirmGate safety layer:
|
|
17
|
+
|
|
18
|
+
1. **Phase 1 — Preview**: Call the tool **without** `env.confirmed`. The server returns `{ status: "pending_confirmation", confirmation_text: "..." }` containing the full operation summary, risk assessment, and irreversible-action warnings.
|
|
19
|
+
2. **Phase 2 — Confirm**: Review `confirmation_text` with the user. Only after explicit user approval, call the tool again **with** `env.confirmed: true` to actually execute the on-chain transaction.
|
|
20
|
+
|
|
21
|
+
> Skipping Phase 1 means the user never sees the risk summary before gas is spent. Always preview first, then confirm. This is especially critical for the `publish: true` step on the ThreeBody Machine (Step 4 in Part 2), which is an irreversible lock of the `machine` and `order_allocators` fields.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## Overview
|
|
8
26
|
|
|
9
27
|
This example demonstrates:
|
|
@@ -23,10 +41,44 @@ This example demonstrates:
|
|
|
23
41
|
|
|
24
42
|
Before running this example, ensure you have:
|
|
25
43
|
|
|
26
|
-
1. **Account Setup**: The `three_body_author`
|
|
44
|
+
1. **Account Setup**: The `three_body_author` and `three_body_customer` accounts with sufficient WOW tokens
|
|
27
45
|
2. **Minimum Balance**: At least 1000 WOW for gas fees and service creation
|
|
28
46
|
|
|
29
|
-
###
|
|
47
|
+
### Step 0a: Generate Accounts
|
|
48
|
+
|
|
49
|
+
If the accounts do not exist locally, generate them first.
|
|
50
|
+
|
|
51
|
+
**Request (generate author account)**:
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"gen": {
|
|
55
|
+
"name": "three_body_author",
|
|
56
|
+
"replaceExistName": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Request (generate customer account)**:
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"gen": {
|
|
65
|
+
"name": "three_body_customer",
|
|
66
|
+
"replaceExistName": true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Expected Result**:
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"gen": {
|
|
75
|
+
"address": "0x...",
|
|
76
|
+
"name": "three_body_author"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 0b: Check Account Balance
|
|
30
82
|
|
|
31
83
|
**Request**:
|
|
32
84
|
```json
|
|
@@ -41,15 +93,24 @@ Before running this example, ensure you have:
|
|
|
41
93
|
**Expected Result** (if account has balance):
|
|
42
94
|
```json
|
|
43
95
|
{
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
96
|
+
"result": {
|
|
97
|
+
"query_type": "account_balance",
|
|
98
|
+
"result": {
|
|
99
|
+
"address": "0x...",
|
|
100
|
+
"name_or_address": "three_body_author",
|
|
101
|
+
"balance": {
|
|
102
|
+
"coinType": "0x2::wow::WOW",
|
|
103
|
+
"coinObjectCount": 3,
|
|
104
|
+
"totalBalance": "3000000000",
|
|
105
|
+
"lockedBalance": {},
|
|
106
|
+
"fundsInAddressBalance": "0"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
48
109
|
}
|
|
49
110
|
}
|
|
50
111
|
```
|
|
51
112
|
|
|
52
|
-
|
|
113
|
+
### Step 0c: Fund Accounts via Faucet (If No Balance)
|
|
53
114
|
|
|
54
115
|
**Request**:
|
|
55
116
|
```json
|
|
@@ -67,15 +128,31 @@ Before running this example, ensure you have:
|
|
|
67
128
|
"faucet": {
|
|
68
129
|
"name_or_address": "three_body_author",
|
|
69
130
|
"result": [
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
{
|
|
132
|
+
"amount": 1000000000,
|
|
133
|
+
"id": "0x...",
|
|
134
|
+
"transferTxDigest": "..."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"amount": 1000000000,
|
|
138
|
+
"id": "0x...",
|
|
139
|
+
"transferTxDigest": "..."
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"amount": 1000000000,
|
|
143
|
+
"id": "0x...",
|
|
144
|
+
"transferTxDigest": "..."
|
|
145
|
+
}
|
|
73
146
|
],
|
|
74
147
|
"network": "testnet"
|
|
75
148
|
}
|
|
76
149
|
}
|
|
77
150
|
```
|
|
78
151
|
|
|
152
|
+
> **Note**: Each faucet result item includes a `transferTxDigest` field representing the on-chain transaction digest for the faucet transfer. This is useful for tracking and verifying the faucet transaction on a block explorer.
|
|
153
|
+
|
|
154
|
+
> **Important**: Repeat the faucet request for `three_body_customer` as well, since Test 2 requires a funded non-author account to attempt the blocked purchase.
|
|
155
|
+
|
|
79
156
|
---
|
|
80
157
|
|
|
81
158
|
## Step 1: Create Permission Object
|
|
@@ -100,19 +177,40 @@ Create a Permission object to manage the service.
|
|
|
100
177
|
},
|
|
101
178
|
"env": {
|
|
102
179
|
"account": "three_body_author",
|
|
103
|
-
"network": "testnet"
|
|
180
|
+
"network": "testnet",
|
|
181
|
+
"confirmed": true
|
|
104
182
|
}
|
|
105
183
|
}
|
|
106
184
|
```
|
|
107
185
|
|
|
186
|
+
> **Permission Index Explanation**:
|
|
187
|
+
> - **`1000`–`1009`**: User-defined permission indexes (starting from `USER_DEFINED_PERM_INDEX_START = 1000`). In this example, `1000` is used for the "Confirm Delivery" forward and `1001` for the "Complete Signature" forward in the Machine workflow (see Step 3). The remaining indexes (`1002`–`1009`) are reserved for future workflow extensions.
|
|
188
|
+
> - **`306`**: The built-in `SERVICE_MACHINE` permission (Service module permission index `306`), which authorizes binding a Machine to a Service. This is required in Step 5 (Configure Machine) where the Machine is bound to the Service.
|
|
189
|
+
>
|
|
190
|
+
> See `BuiltinPermissionIndex` in `ts-sdk/packages/wowok/src/w/call/permission.ts` for the full list of built-in permission indexes.
|
|
191
|
+
|
|
192
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt (it unbinds any existing name). Without `confirmed: true`, the operation will be blocked pending user confirmation. This applies to all subsequent steps using `replaceExistName: true` or `publish: true`.
|
|
193
|
+
|
|
108
194
|
**Expected Result**:
|
|
109
195
|
```json
|
|
110
|
-
[
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
"type": "Permission",
|
|
199
|
+
"type_raw": "0x2::permission::Permission",
|
|
200
|
+
"object": "0x...",
|
|
201
|
+
"version": "...",
|
|
202
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
203
|
+
"change": "created"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"type": "TableItem_PermissionPerm",
|
|
207
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::parent_linked_table::Node<address, vector<u16>>>",
|
|
208
|
+
"object": "0x...",
|
|
209
|
+
"version": "...",
|
|
210
|
+
"owner": {"ObjectOwner": "0x..."},
|
|
211
|
+
"change": "created"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
116
214
|
```
|
|
117
215
|
|
|
118
216
|
---
|
|
@@ -128,10 +226,10 @@ Create a Guard that verifies the buyer is the service creator (author). This ens
|
|
|
128
226
|
"data": {
|
|
129
227
|
"namedNew": {
|
|
130
228
|
"name": "three_body_buy_guard",
|
|
131
|
-
"tags": ["signature", "book", "buy-guard"],
|
|
229
|
+
"tags": ["signature", "book", "buy-guard", "level1-strict"],
|
|
132
230
|
"replaceExistName": true
|
|
133
231
|
},
|
|
134
|
-
"description": "Verify buyer is the service creator (three_body_author). Only the author can purchase this signature service.",
|
|
232
|
+
"description": "Verify buyer is the service creator (three_body_author). Only the author can purchase this signature service. VERIFIER CONSTRAINT LEVEL 1 (strict single-identity binding): The author role is permanently tied to a single address. The designer explicitly accepts the lock-in risk because (a) the author is the sole service operator in this minimal example, and (b) Guard immutability guarantees the buyer whitelist cannot be tampered with. R-C4-04 (info): if the author address is lost or rotated, the Guard must be rebuilt and the Service's buy_guard must be re-bound.",
|
|
135
233
|
"table": [
|
|
136
234
|
{
|
|
137
235
|
"identifier": 0,
|
|
@@ -157,21 +255,30 @@ Create a Guard that verifies the buyer is the service creator (author). This ens
|
|
|
157
255
|
},
|
|
158
256
|
"env": {
|
|
159
257
|
"account": "three_body_author",
|
|
160
|
-
"network": "testnet"
|
|
258
|
+
"network": "testnet",
|
|
259
|
+
"confirmed": true
|
|
161
260
|
}
|
|
162
261
|
}
|
|
163
262
|
```
|
|
164
263
|
|
|
165
264
|
> **Note**: The Guard uses a `table` to define constant values with identifiers, then references them in the `root` logic using `identifier` node type. The `root` is a direct GuardNode (no wrapper).
|
|
166
265
|
|
|
266
|
+
> **⚠️ Level 1 — Strict Single-Identity Binding (R-C4-04)**: This Guard uses `logic_equal[context(Signer), identifier[0](three_body_author)]` — the strictest verifier constraint level. Only the single fixed author address can pass. The lock-in risk is acceptable here because: (1) the author is the sole operator of this signature service, (2) Guard immutability guarantees the whitelist cannot be tampered with, and (3) the buy_guard can be re-bound on the Service if the author address ever needs rotation (the old Guard is abandoned and a new one is created). For multi-operator scenarios, prefer Level 2 (identity-set binding) instead.
|
|
267
|
+
|
|
268
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
269
|
+
|
|
167
270
|
**Expected Result**:
|
|
168
271
|
```json
|
|
169
|
-
[
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
272
|
+
[
|
|
273
|
+
{
|
|
274
|
+
"type": "Guard",
|
|
275
|
+
"type_raw": "0x2::guard::Guard",
|
|
276
|
+
"object": "0x...",
|
|
277
|
+
"version": "...",
|
|
278
|
+
"owner": "Immutable",
|
|
279
|
+
"change": "created"
|
|
280
|
+
}
|
|
281
|
+
]
|
|
175
282
|
```
|
|
176
283
|
|
|
177
284
|
---
|
|
@@ -234,19 +341,50 @@ Create a Machine to define the service workflow: Book Delivery → Signature Com
|
|
|
234
341
|
},
|
|
235
342
|
"env": {
|
|
236
343
|
"account": "three_body_author",
|
|
237
|
-
"network": "testnet"
|
|
344
|
+
"network": "testnet",
|
|
345
|
+
"confirmed": true
|
|
238
346
|
}
|
|
239
347
|
}
|
|
240
348
|
```
|
|
241
349
|
|
|
350
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt. Additionally, `publish: true` on the Machine is an irreversible operation.
|
|
351
|
+
|
|
242
352
|
**Expected Result**:
|
|
243
353
|
```json
|
|
244
|
-
[
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
354
|
+
[
|
|
355
|
+
{
|
|
356
|
+
"type": "TableItem_MachineNode",
|
|
357
|
+
"type_raw": "0x2::dynamic_field::Field<0x1::string::String, 0x2::parent_linked_table::Node<0x1::string::String, vector<0x2::machine::NodePair>>>",
|
|
358
|
+
"object": "0x...",
|
|
359
|
+
"version": "...",
|
|
360
|
+
"owner": {"ObjectOwner": "0x..."},
|
|
361
|
+
"change": "created"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"type": "TableItem_MachineNode",
|
|
365
|
+
"type_raw": "0x2::dynamic_field::Field<0x1::string::String, 0x2::parent_linked_table::Node<0x1::string::String, vector<0x2::machine::NodePair>>>",
|
|
366
|
+
"object": "0x...",
|
|
367
|
+
"version": "...",
|
|
368
|
+
"owner": {"ObjectOwner": "0x..."},
|
|
369
|
+
"change": "created"
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"type": "Machine",
|
|
373
|
+
"type_raw": "0x2::machine::Machine",
|
|
374
|
+
"object": "0x...",
|
|
375
|
+
"version": "...",
|
|
376
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
377
|
+
"change": "created"
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"type": "TableItem_EntityLinker",
|
|
381
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
382
|
+
"object": "0x...",
|
|
383
|
+
"version": "...",
|
|
384
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
385
|
+
"change": "created"
|
|
386
|
+
}
|
|
387
|
+
]
|
|
250
388
|
```
|
|
251
389
|
|
|
252
390
|
---
|
|
@@ -271,19 +409,34 @@ Create the Three-Body signature service without publishing. The Service must be
|
|
|
271
409
|
},
|
|
272
410
|
"env": {
|
|
273
411
|
"account": "three_body_author",
|
|
274
|
-
"network": "testnet"
|
|
412
|
+
"network": "testnet",
|
|
413
|
+
"confirmed": true
|
|
275
414
|
}
|
|
276
415
|
}
|
|
277
416
|
```
|
|
278
417
|
|
|
418
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
419
|
+
|
|
279
420
|
**Expected Result**:
|
|
280
421
|
```json
|
|
281
|
-
[
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
422
|
+
[
|
|
423
|
+
{
|
|
424
|
+
"type": "TableItem_EntityLinker",
|
|
425
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
426
|
+
"object": "0x...",
|
|
427
|
+
"version": "...",
|
|
428
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
429
|
+
"change": "mutated"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"type": "Service",
|
|
433
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
434
|
+
"object": "0x...",
|
|
435
|
+
"version": "...",
|
|
436
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
437
|
+
"change": "created"
|
|
438
|
+
}
|
|
439
|
+
]
|
|
287
440
|
```
|
|
288
441
|
|
|
289
442
|
---
|
|
@@ -309,12 +462,24 @@ Bind the published Machine to the Service. **Important**: The Service must be un
|
|
|
309
462
|
|
|
310
463
|
**Expected Result**:
|
|
311
464
|
```json
|
|
312
|
-
[
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
465
|
+
[
|
|
466
|
+
{
|
|
467
|
+
"type": "Service",
|
|
468
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
469
|
+
"object": "0x...",
|
|
470
|
+
"version": "...",
|
|
471
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
472
|
+
"change": "mutated"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"type": "TableItem_EntityLinker",
|
|
476
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
477
|
+
"object": "0x...",
|
|
478
|
+
"version": "...",
|
|
479
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
480
|
+
"change": "created"
|
|
481
|
+
}
|
|
482
|
+
]
|
|
318
483
|
```
|
|
319
484
|
|
|
320
485
|
---
|
|
@@ -340,19 +505,173 @@ Configure the Buy Guard to restrict purchases to the author only.
|
|
|
340
505
|
|
|
341
506
|
**Expected Result**:
|
|
342
507
|
```json
|
|
343
|
-
[
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
508
|
+
[
|
|
509
|
+
{
|
|
510
|
+
"type": "Service",
|
|
511
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
512
|
+
"object": "0x...",
|
|
513
|
+
"version": "...",
|
|
514
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
515
|
+
"change": "mutated"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"type": "TableItem_EntityLinker",
|
|
519
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
520
|
+
"object": "0x...",
|
|
521
|
+
"version": "...",
|
|
522
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
523
|
+
"change": "created"
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Step 6.5: Create Treasury Object
|
|
531
|
+
|
|
532
|
+
Create a Treasury object to aggregate signature service revenue (public funds for the author's operational distribution). The Treasury uses the same Permission as the Service (`three_body_permission`) — this ensures a single consistent permission organization governs both fund collection and service operations.
|
|
533
|
+
|
|
534
|
+
**Request**:
|
|
535
|
+
```json
|
|
536
|
+
{
|
|
537
|
+
"operation_type": "treasury",
|
|
538
|
+
"data": {
|
|
539
|
+
"object": {
|
|
540
|
+
"name": "three_body_treasury",
|
|
541
|
+
"type_parameter": "0x2::wow::WOW",
|
|
542
|
+
"permission": "three_body_permission",
|
|
543
|
+
"replaceExistName": true
|
|
544
|
+
},
|
|
545
|
+
"description": "Treasury for aggregating Three-Body signature service revenue (author's public funds for operations and distribution). Uses the same Permission as the Service (three_body_permission) for consistency — a single permission organization governs both fund collection and service operations."
|
|
546
|
+
},
|
|
547
|
+
"env": {
|
|
548
|
+
"account": "three_body_author",
|
|
549
|
+
"network": "testnet",
|
|
550
|
+
"confirmed": true
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
> **Treasury-First Rule**: Following the fund-flow design pattern established in the Insurance, MyShop_Advanced, and Travel examples, merchant revenue flows to `three_body_treasury` (not directly to the author's address or the Service address). This:
|
|
556
|
+
> 1. **Aggregates public funds** for the author's operational distribution and accounting
|
|
557
|
+
> 2. **Makes allocators inherently safe** (R-C3-06) — funds always flow to the fixed Treasury regardless of caller, so no Signer binding is needed in the allocator Guard
|
|
558
|
+
> 3. **Uses permission consistency** — Treasury and Service share `three_body_permission`, ensuring unified governance
|
|
559
|
+
|
|
560
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
561
|
+
|
|
562
|
+
**Expected Result**:
|
|
563
|
+
```json
|
|
564
|
+
[
|
|
565
|
+
{
|
|
566
|
+
"type": "Treasury",
|
|
567
|
+
"type_raw": "0x2::treasury::Treasury<0x2::wow::WOW>",
|
|
568
|
+
"object": "0x...",
|
|
569
|
+
"version": "...",
|
|
570
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
571
|
+
"change": "created"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
"type": "TableItem_EntityLinker",
|
|
575
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
576
|
+
"object": "0x...",
|
|
577
|
+
"version": "...",
|
|
578
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
579
|
+
"change": "created"
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Step 6.6: Create Allocator Guard
|
|
587
|
+
|
|
588
|
+
Create a dedicated Guard for the order allocator that verifies the order belongs to this service. This is a **Level 3 scene-combined** Guard: no Signer binding is needed because the allocator uses `sharing.who=Entity(three_body_treasury)` — funds always flow to the fixed Treasury regardless of who triggers the allocation.
|
|
589
|
+
|
|
590
|
+
**Guard Logic**:
|
|
591
|
+
```
|
|
592
|
+
order.service == three_body_signature_service
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**Request**:
|
|
596
|
+
```json
|
|
597
|
+
{
|
|
598
|
+
"operation_type": "guard",
|
|
599
|
+
"data": {
|
|
600
|
+
"namedNew": {
|
|
601
|
+
"name": "three_body_allocator_guard",
|
|
602
|
+
"tags": ["signature", "book", "allocator", "level3-scene-combined"],
|
|
603
|
+
"replaceExistName": true
|
|
604
|
+
},
|
|
605
|
+
"description": "Allocator guard for Three-Body signature service: verifies order.service == three_body_signature_service to prevent cross-service theft (R-C3-05). VERIFIER CONSTRAINT LEVEL 3 (scene-combined): No Signer binding needed because the allocator uses sharing.who=Entity(three_body_treasury) — funds always flow to the Treasury regardless of caller (R-C3-06 safe).",
|
|
606
|
+
"table": [
|
|
607
|
+
{
|
|
608
|
+
"identifier": 0,
|
|
609
|
+
"b_submission": true,
|
|
610
|
+
"value_type": "Address",
|
|
611
|
+
"name": "Order ID (submitted at runtime)"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
"identifier": 1,
|
|
615
|
+
"b_submission": false,
|
|
616
|
+
"value_type": "Address",
|
|
617
|
+
"value": "three_body_signature_service",
|
|
618
|
+
"name": "Service address (this service)"
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
"root": {
|
|
622
|
+
"type": "logic_equal",
|
|
623
|
+
"nodes": [
|
|
624
|
+
{
|
|
625
|
+
"type": "query",
|
|
626
|
+
"query": "order.service",
|
|
627
|
+
"object": {"identifier": 0},
|
|
628
|
+
"parameters": []
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"type": "identifier",
|
|
632
|
+
"identifier": 1
|
|
633
|
+
}
|
|
634
|
+
]
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
"env": {
|
|
638
|
+
"account": "three_body_author",
|
|
639
|
+
"network": "testnet",
|
|
640
|
+
"confirmed": true
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Guard Explanation (Service Ownership Check — Level 3 Scene-Combined):**
|
|
646
|
+
- **Table Item 0**: Order address (submitted at runtime, `b_submission: true`) — the order being allocated
|
|
647
|
+
- **Table Item 1**: Constant address `three_body_signature_service` (this service's on-chain address)
|
|
648
|
+
- **Root**: `logic_equal[query("order.service"), identifier[1]]` — verifies the submitted Order's `service` field equals `three_body_signature_service`
|
|
649
|
+
|
|
650
|
+
> **Risk Elimination (R-C3-05 + R-C3-06) — Level 3 Scene-Combined Design**:
|
|
651
|
+
> - **R-C3-05 (Cross-service theft)**: Eliminated by the Service Ownership check. An attacker cannot submit another service's order because `order.service` won't match `three_body_signature_service`.
|
|
652
|
+
> - **R-C3-06 (Fund theft via Signer)**: Eliminated by the scene itself — the allocator uses `"who": {"Entity": {"name_or_address": "three_body_treasury"}}` (funds flow to the fixed Treasury address). Funds go to a fixed recipient regardless of caller, so **no Signer binding is needed**. This is the Level 3 scene-combined pattern.
|
|
653
|
+
|
|
654
|
+
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
655
|
+
|
|
656
|
+
**Expected Result**:
|
|
657
|
+
```json
|
|
658
|
+
[
|
|
659
|
+
{
|
|
660
|
+
"type": "Guard",
|
|
661
|
+
"type_raw": "0x2::guard::Guard",
|
|
662
|
+
"object": "0x...",
|
|
663
|
+
"version": "...",
|
|
664
|
+
"owner": "Immutable",
|
|
665
|
+
"change": "created"
|
|
666
|
+
}
|
|
667
|
+
]
|
|
349
668
|
```
|
|
350
669
|
|
|
351
670
|
---
|
|
352
671
|
|
|
353
672
|
## Step 7: Configure Order Allocators
|
|
354
673
|
|
|
355
|
-
Set up fund allocation: 100% to the author upon order completion.
|
|
674
|
+
Set up fund allocation: 100% to the author's Treasury upon order completion.
|
|
356
675
|
|
|
357
676
|
**Request**:
|
|
358
677
|
```json
|
|
@@ -361,15 +680,15 @@ Set up fund allocation: 100% to the author upon order completion.
|
|
|
361
680
|
"data": {
|
|
362
681
|
"object": "three_body_signature_service",
|
|
363
682
|
"order_allocators": {
|
|
364
|
-
"description": "Three-Body signature service fund allocation - 100% to author",
|
|
683
|
+
"description": "Three-Body signature service fund allocation - 100% to author Treasury",
|
|
365
684
|
"threshold": 0,
|
|
366
685
|
"allocators": [
|
|
367
686
|
{
|
|
368
|
-
"guard": "
|
|
687
|
+
"guard": "three_body_allocator_guard",
|
|
369
688
|
"sharing": [
|
|
370
689
|
{
|
|
371
690
|
"who": {
|
|
372
|
-
"
|
|
691
|
+
"Entity": {"name_or_address": "three_body_treasury"}
|
|
373
692
|
},
|
|
374
693
|
"sharing": 10000,
|
|
375
694
|
"mode": "Rate"
|
|
@@ -387,14 +706,23 @@ Set up fund allocation: 100% to the author upon order completion.
|
|
|
387
706
|
}
|
|
388
707
|
```
|
|
389
708
|
|
|
709
|
+
> **⚠️ Risk Elimination — Why this configuration is safe**:
|
|
710
|
+
> - **R-C3-05 (Cross-service theft)**: Eliminated by `three_body_allocator_guard` (Step 6.6), which verifies `order.service == three_body_signature_service` before allocation proceeds.
|
|
711
|
+
> - **R-C3-06 (Fund theft via Signer)**: Eliminated by `sharing.who = {"Entity": {"name_or_address": "three_body_treasury"}}` — funds always flow to the fixed Treasury address regardless of who triggers the allocation. An attacker cannot redirect funds to themselves even if they somehow bypass the Guard.
|
|
712
|
+
> - **Previous unsafe pattern (DO NOT USE)**: The original design used `guard: "three_body_buy_guard"` (no `order.service` check) with `sharing.who = {"Signer": "signer"}` — this allowed anyone to trigger allocation of any order's funds to themselves.
|
|
713
|
+
|
|
390
714
|
**Expected Result**:
|
|
391
715
|
```json
|
|
392
|
-
[
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
716
|
+
[
|
|
717
|
+
{
|
|
718
|
+
"type": "Service",
|
|
719
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
720
|
+
"object": "0x...",
|
|
721
|
+
"version": "...",
|
|
722
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
723
|
+
"change": "mutated"
|
|
724
|
+
}
|
|
725
|
+
]
|
|
398
726
|
```
|
|
399
727
|
|
|
400
728
|
---
|
|
@@ -426,19 +754,26 @@ Add sales items and publish the service to make it available for orders.
|
|
|
426
754
|
},
|
|
427
755
|
"env": {
|
|
428
756
|
"account": "three_body_author",
|
|
429
|
-
"network": "testnet"
|
|
757
|
+
"network": "testnet",
|
|
758
|
+
"confirmed": true
|
|
430
759
|
}
|
|
431
760
|
}
|
|
432
761
|
```
|
|
433
762
|
|
|
763
|
+
> **Important**: `env.confirmed: true` is **critical** here because `publish: true` is an irreversible operation that locks the `machine` and `order_allocators` fields. The server will block the transaction with a "Publish confirmation (irreversible lock)" prompt until `confirmed: true` is provided.
|
|
764
|
+
|
|
434
765
|
**Expected Result**:
|
|
435
766
|
```json
|
|
436
|
-
[
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
767
|
+
[
|
|
768
|
+
{
|
|
769
|
+
"type": "Service",
|
|
770
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
771
|
+
"object": "0x...",
|
|
772
|
+
"version": "...",
|
|
773
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
774
|
+
"change": "mutated"
|
|
775
|
+
}
|
|
776
|
+
]
|
|
442
777
|
```
|
|
443
778
|
|
|
444
779
|
---
|
|
@@ -464,12 +799,16 @@ Unpause the service to allow order creation.
|
|
|
464
799
|
|
|
465
800
|
**Expected Result**:
|
|
466
801
|
```json
|
|
467
|
-
[
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
802
|
+
[
|
|
803
|
+
{
|
|
804
|
+
"type": "Service",
|
|
805
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
806
|
+
"object": "0x...",
|
|
807
|
+
"version": "...",
|
|
808
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
809
|
+
"change": "mutated"
|
|
810
|
+
}
|
|
811
|
+
]
|
|
473
812
|
```
|
|
474
813
|
|
|
475
814
|
---
|
|
@@ -491,28 +830,77 @@ Query the service to verify all configurations.
|
|
|
491
830
|
**Expected Result**:
|
|
492
831
|
```json
|
|
493
832
|
{
|
|
494
|
-
"
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
833
|
+
"result": {
|
|
834
|
+
"query_type": "onchain_objects",
|
|
835
|
+
"result": {
|
|
836
|
+
"objects": [
|
|
837
|
+
{
|
|
838
|
+
"object": "0x...",
|
|
839
|
+
"type": "Service",
|
|
840
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
841
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
842
|
+
"version": "...",
|
|
843
|
+
"previousTransaction": "...",
|
|
844
|
+
"description": "Three-Body author book signature service. Provide a message up to 10 characters, and the author will sign your book. Process: 1.Book Delivery 2.Signature Completion. Fee: 888.",
|
|
845
|
+
"location": "",
|
|
846
|
+
"sales": [
|
|
847
|
+
{
|
|
848
|
+
"name": "Three-Body Book Signature",
|
|
849
|
+
"stock": "100",
|
|
850
|
+
"suspension": false,
|
|
851
|
+
"price": "888",
|
|
852
|
+
"wip": "",
|
|
853
|
+
"wip_hash": ""
|
|
854
|
+
}
|
|
855
|
+
],
|
|
856
|
+
"repositories": [],
|
|
857
|
+
"buy_guard": "0x...",
|
|
858
|
+
"machine": "0x...",
|
|
859
|
+
"bPublished": true,
|
|
860
|
+
"bPaused": false,
|
|
861
|
+
"customer_required": ["phone", "email", "shipping_address"],
|
|
862
|
+
"arbitrations": [],
|
|
863
|
+
"compensation_fund": "0",
|
|
864
|
+
"paused_time": null,
|
|
865
|
+
"setting_lock_duration": "2592000000",
|
|
866
|
+
"order_allocators": {
|
|
867
|
+
"description": "Three-Body signature service fund allocation - 100% to author Treasury",
|
|
868
|
+
"threshold": "0",
|
|
869
|
+
"allocators": [
|
|
870
|
+
{
|
|
871
|
+
"guard": "0x...",
|
|
872
|
+
"sharing": [
|
|
873
|
+
{
|
|
874
|
+
"who": {"Entity": "0x..."},
|
|
875
|
+
"sharing": "10000",
|
|
876
|
+
"mode": 1
|
|
877
|
+
}
|
|
878
|
+
],
|
|
879
|
+
"fix": "0",
|
|
880
|
+
"max": null
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
},
|
|
884
|
+
"rewards": [],
|
|
885
|
+
"um": null,
|
|
886
|
+
"permission": "0x...",
|
|
887
|
+
"cache_expire": 1234567890,
|
|
888
|
+
"query_name": "three_body_signature_service"
|
|
889
|
+
}
|
|
890
|
+
]
|
|
505
891
|
}
|
|
506
|
-
|
|
507
|
-
"buy_guard": "three_body_buy_guard",
|
|
508
|
-
"machine": "three_body_machine",
|
|
509
|
-
"bPublished": true,
|
|
510
|
-
"bPaused": false,
|
|
511
|
-
"customer_required": ["phone", "email", "shipping_address"],
|
|
512
|
-
"permission": "three_body_permission"
|
|
892
|
+
}
|
|
513
893
|
}
|
|
514
894
|
```
|
|
515
895
|
|
|
896
|
+
> **Field Reference**:
|
|
897
|
+
> - **`buy_guard`**, **`machine`**, **`permission`**: Return **on-chain object IDs** (not names). The on-chain data stores raw object IDs; resolving them back to local mark names requires a separate reverse lookup that is not performed by `onchain_objects` queries.
|
|
898
|
+
> - **`query_name`**: The original name string passed in the query request (here, `"three_body_signature_service"`). This is automatically populated by the SDK from the input `objects` array, so you can identify which queried name corresponds to which returned object.
|
|
899
|
+
> - **`order_allocators.allocators[].guard`**: Returns the on-chain object ID of `three_body_allocator_guard` (created in Step 6.6). This Guard verifies `order.service == three_body_signature_service` (R-C3-05 protection).
|
|
900
|
+
> - **`order_allocators.allocators[].sharing[].who`**: `{"Entity": "0x..."}` indicates funds flow to the fixed Treasury object (`three_body_treasury` from Step 6.5). The address is the Treasury's on-chain object ID. This eliminates R-C3-06 (fund theft via Signer) because the recipient is fixed regardless of caller.
|
|
901
|
+
> - **`order_allocators.allocators[].sharing[].mode`**: `1` is the numeric enum for `Rate` mode (input accepts the string `"Rate"`, output returns the numeric `1`).
|
|
902
|
+
> - **`order_allocators.allocators[].fix`** and **`max`**: Additional fields returned on-chain (default `"0"` and `null` respectively) that are not part of the input schema but are present in the on-chain data structure.
|
|
903
|
+
|
|
516
904
|
---
|
|
517
905
|
|
|
518
906
|
## Testing the Buy Guard
|
|
@@ -565,26 +953,78 @@ The author (`three_body_author`) should be able to purchase the service.
|
|
|
565
953
|
```json
|
|
566
954
|
[
|
|
567
955
|
{
|
|
568
|
-
"type": "
|
|
569
|
-
"
|
|
956
|
+
"type": "Service",
|
|
957
|
+
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
958
|
+
"object": "0x...",
|
|
959
|
+
"version": "...",
|
|
960
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
961
|
+
"change": "mutated"
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
"type": "TableItem_EntityLinker",
|
|
965
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
966
|
+
"object": "0x...",
|
|
967
|
+
"version": "...",
|
|
968
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
969
|
+
"change": "mutated"
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"type": "TableItem_EntityLinker",
|
|
973
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
974
|
+
"object": "0x...",
|
|
975
|
+
"version": "...",
|
|
976
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
977
|
+
"change": "mutated"
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
"type": "TableItem_EntityLinker",
|
|
981
|
+
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
982
|
+
"object": "0x...",
|
|
570
983
|
"version": "...",
|
|
984
|
+
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
985
|
+
"change": "created"
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
"type": "Allocation",
|
|
989
|
+
"type_raw": "0x2::allocation::Allocation<0x2::wow::WOW>",
|
|
990
|
+
"object": "0x...",
|
|
991
|
+
"version": "...",
|
|
992
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
571
993
|
"change": "created"
|
|
572
994
|
},
|
|
573
995
|
{
|
|
574
996
|
"type": "Order",
|
|
575
|
-
"
|
|
997
|
+
"type_raw": "0x2::order::Order",
|
|
998
|
+
"object": "0x...",
|
|
576
999
|
"version": "...",
|
|
1000
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
577
1001
|
"change": "created"
|
|
578
1002
|
},
|
|
579
1003
|
{
|
|
580
|
-
"type": "
|
|
581
|
-
"
|
|
1004
|
+
"type": "Progress",
|
|
1005
|
+
"type_raw": "0x2::progress::Progress",
|
|
1006
|
+
"object": "0x...",
|
|
582
1007
|
"version": "...",
|
|
1008
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
583
1009
|
"change": "created"
|
|
584
1010
|
}
|
|
585
1011
|
]
|
|
586
1012
|
```
|
|
587
1013
|
|
|
1014
|
+
> **Why So Many Objects? (Information Injection Design)**
|
|
1015
|
+
>
|
|
1016
|
+
> The `order_new` operation intentionally returns ALL objects affected by the transaction, not just the primary created objects. This is a deliberate "information injection" design so callers get a complete view of the state changes:
|
|
1017
|
+
>
|
|
1018
|
+
> 1. **`Service` (mutated)**: The service's inventory/stock is decremented as a side effect of the purchase.
|
|
1019
|
+
> 2. **`TableItem_EntityLinker` ×3 (mutated/created)**: The order process links the buyer, the service, and the buy_guard entities via the on-chain `EntityLinker` registrar (owner `0xaaa`). These are the entity-graph edges created/updated by the order.
|
|
1020
|
+
> 3. **`Allocation` (created)**: The fund allocation object that will distribute the payment according to `order_allocators`.
|
|
1021
|
+
> 4. **`Order` (created)**: The primary order object tracking the purchase.
|
|
1022
|
+
> 5. **`Progress` (created)**: The workflow progress object tracking the order through the Machine nodes.
|
|
1023
|
+
>
|
|
1024
|
+
> The returned objects use **on-chain object IDs** (not the names assigned via `namedNewOrder`/`namedNewProgress`/`namedNewAllocation`). To resolve an object ID back to its local mark name, use the `query_toolkit` with `query_type: "local_names"` and pass the addresses array.
|
|
1025
|
+
>
|
|
1026
|
+
> The return order reflects the transaction's execution order: Service (inventory update) → EntityLinker (entity graph edges) → Allocation → Order → Progress, following the dependency chain of object creation.
|
|
1027
|
+
|
|
588
1028
|
---
|
|
589
1029
|
|
|
590
1030
|
### Test 2: Non-Author Purchase (Should Fail)
|
|
@@ -620,12 +1060,24 @@ Any other account attempting to purchase should fail with Buy Guard verification
|
|
|
620
1060
|
```
|
|
621
1061
|
|
|
622
1062
|
**Expected Result** (Error):
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
1063
|
+
```
|
|
1064
|
+
Error: Error Description: Verification failed
|
|
1065
|
+
Transaction resolution failed: MoveAbort in 8th command, abort code: 7 (Verify failed), in '0x0000000000000000000000000000000000000000000000000000000000000002::passport::result_for_guard' (instruction 17)
|
|
627
1066
|
```
|
|
628
1067
|
|
|
1068
|
+
> **Error Format Explained (Information Injection Design)**
|
|
1069
|
+
>
|
|
1070
|
+
> The error response is intentionally enriched with detailed diagnostic information via the `enrichMoveError` function in the SDK. This is a deliberate "information injection" design so callers can precisely diagnose failures without needing to replay the transaction:
|
|
1071
|
+
>
|
|
1072
|
+
> - **`Error: ` prefix**: Added by the MCP handler (`handler.ts`) to mark the response as an error.
|
|
1073
|
+
> - **`Error Description: Verification failed`**: A human-readable classification of the error category, generated by the `classifyError` logic.
|
|
1074
|
+
> - **`Transaction resolution failed: MoveAbort in 8th command`**: The raw Sui SDK error indicating which transaction command (8th) aborted.
|
|
1075
|
+
> - **`abort code: 7 (Verify failed)`**: The Move abort code (7) translated to its semantic meaning (`Verify failed`).
|
|
1076
|
+
> - **`0x0000...0002::passport::result_for_guard`**: The fully-qualified module path using the 64-character hex address form (the Sui SDK returns the full canonical form, not the short `0x2` shorthand).
|
|
1077
|
+
> - **`(instruction 17)`**: The specific instruction index within the Move function where the abort occurred.
|
|
1078
|
+
>
|
|
1079
|
+
> The error is returned as a **plain text string** (not a JSON object), because it combines multiple layers of diagnostic information from different sources (Sui SDK, Move VM, MCP handler). Callers detecting errors should check for the `Error:` prefix or use the `isError` flag on the MCP response.
|
|
1080
|
+
|
|
629
1081
|
---
|
|
630
1082
|
|
|
631
1083
|
## Workflow Execution
|
|
@@ -651,19 +1103,34 @@ The author confirms the book has been delivered.
|
|
|
651
1103
|
},
|
|
652
1104
|
"env": {
|
|
653
1105
|
"account": "three_body_author",
|
|
654
|
-
"network": "testnet"
|
|
1106
|
+
"network": "testnet",
|
|
1107
|
+
"no_cache": true
|
|
655
1108
|
}
|
|
656
1109
|
}
|
|
657
1110
|
```
|
|
658
1111
|
|
|
1112
|
+
> **Tip**: Although `no_cache: true` is most critical for the second Progress operation (Node 2), it is recommended to use it for **all** sequential Progress operations on the same object to ensure the SDK always reads the latest on-chain state.
|
|
1113
|
+
|
|
659
1114
|
**Expected Result**:
|
|
660
1115
|
```json
|
|
661
|
-
[
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1116
|
+
[
|
|
1117
|
+
{
|
|
1118
|
+
"type": "Progress",
|
|
1119
|
+
"type_raw": "0x2::progress::Progress",
|
|
1120
|
+
"object": "0x...",
|
|
1121
|
+
"version": "...",
|
|
1122
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1123
|
+
"change": "mutated"
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
"type": "TableItem_ProgressHistory",
|
|
1127
|
+
"type_raw": "0x2::dynamic_field::Field<u64, 0x2::progress::History>",
|
|
1128
|
+
"object": "0x...",
|
|
1129
|
+
"version": "...",
|
|
1130
|
+
"owner": {"ObjectOwner": "0x..."},
|
|
1131
|
+
"change": "created"
|
|
1132
|
+
}
|
|
1133
|
+
]
|
|
667
1134
|
```
|
|
668
1135
|
|
|
669
1136
|
### Node 2: Signature Completed
|
|
@@ -695,12 +1162,24 @@ The author completes the signature.
|
|
|
695
1162
|
|
|
696
1163
|
**Expected Result**:
|
|
697
1164
|
```json
|
|
698
|
-
[
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1165
|
+
[
|
|
1166
|
+
{
|
|
1167
|
+
"type": "Progress",
|
|
1168
|
+
"type_raw": "0x2::progress::Progress",
|
|
1169
|
+
"object": "0x...",
|
|
1170
|
+
"version": "...",
|
|
1171
|
+
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1172
|
+
"change": "mutated"
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
"type": "TableItem_ProgressHistory",
|
|
1176
|
+
"type_raw": "0x2::dynamic_field::Field<u64, 0x2::progress::History>",
|
|
1177
|
+
"object": "0x...",
|
|
1178
|
+
"version": "...",
|
|
1179
|
+
"owner": {"ObjectOwner": "0x..."},
|
|
1180
|
+
"change": "created"
|
|
1181
|
+
}
|
|
1182
|
+
]
|
|
704
1183
|
```
|
|
705
1184
|
|
|
706
1185
|
---
|
|
@@ -709,23 +1188,34 @@ The author completes the signature.
|
|
|
709
1188
|
|
|
710
1189
|
This example demonstrates:
|
|
711
1190
|
|
|
712
|
-
1. **Buy Guard Implementation**: Restricts service purchases to specific accounts
|
|
1191
|
+
1. **Buy Guard Implementation**: Restricts service purchases to specific accounts (Level 1 strict single-identity binding)
|
|
713
1192
|
2. **Machine Workflow**: Two-node process for service delivery tracking
|
|
714
1193
|
3. **WIP Files Optional**: Sales items can use WIP files or empty strings
|
|
715
1194
|
4. **Service Configuration**: Complete setup from creation to publication
|
|
1195
|
+
5. **Safe Fund Allocation**: Treasury-first design with Level 3 scene-combined allocator Guard — funds always flow to the fixed Treasury, eliminating R-C3-05 (cross-service theft) and R-C3-06 (fund theft via Signer)
|
|
716
1196
|
|
|
717
1197
|
### Key Objects
|
|
718
1198
|
|
|
719
1199
|
| Object | Name |
|
|
720
1200
|
|--------|------|
|
|
721
1201
|
| Permission | three_body_permission |
|
|
722
|
-
| Buy Guard | three_body_buy_guard |
|
|
1202
|
+
| Buy Guard | three_body_buy_guard (Level 1 strict, R-C4-04) |
|
|
723
1203
|
| Machine | three_body_machine |
|
|
724
1204
|
| Service | three_body_signature_service |
|
|
1205
|
+
| Treasury | three_body_treasury |
|
|
1206
|
+
| Allocator Guard | three_body_allocator_guard (Level 3 scene-combined, R-C3-05/R-C3-06 safe) |
|
|
725
1207
|
| Order | three_body_order |
|
|
726
1208
|
| Allocation | three_body_allocation |
|
|
727
1209
|
| Progress | three_body_progress |
|
|
728
1210
|
|
|
1211
|
+
### Risk Mitigation Summary
|
|
1212
|
+
|
|
1213
|
+
| Risk | Severity | Mitigation |
|
|
1214
|
+
|------|----------|------------|
|
|
1215
|
+
| **R-C3-05** (Cross-service theft) | High | `three_body_allocator_guard` verifies `order.service == three_body_signature_service` before allocation |
|
|
1216
|
+
| **R-C3-06** (Fund theft via Signer) | Critical | `sharing.who = {"Entity": {"name_or_address": "three_body_treasury"}}` — funds flow to fixed Treasury, no Signer binding needed |
|
|
1217
|
+
| **R-C4-04** (Level 1 lock-in) | Info | Buy Guard uses Level 1 strict binding (justified: sole-operator service, buy_guard can be re-bound if author rotates) |
|
|
1218
|
+
|
|
729
1219
|
---
|
|
730
1220
|
|
|
731
1221
|
## Design Notes
|
|
@@ -760,10 +1250,22 @@ Each node transition requires the author's confirmation, ensuring accountability
|
|
|
760
1250
|
- Permission first (foundation)
|
|
761
1251
|
- Machine (create workflow before service)
|
|
762
1252
|
- Service (unpublished)
|
|
763
|
-
- Guards (
|
|
764
|
-
-
|
|
1253
|
+
- Guards (Buy Guard for purchase control; Allocator Guard needs Service address for `order.service` verification)
|
|
1254
|
+
- Treasury (uses same Permission as Service for unified governance)
|
|
1255
|
+
- Configure Service (add machine, buy_guard, order_allocators with allocator guard + Entity(Treasury))
|
|
765
1256
|
- Publish Service (LAST - once published, many changes are blocked)
|
|
766
1257
|
|
|
767
|
-
3. **
|
|
1258
|
+
3. **Treasury-First Fund Flow**: Always route merchant revenue through a Treasury object using `sharing.who = {"Entity": {"name_or_address": "treasury_name"}}` instead of `{"Signer": "signer"}`. This eliminates R-C3-06 (critical fund theft via Signer) because funds flow to a fixed recipient regardless of who triggers the allocation. Combined with an allocator Guard that verifies `order.service == this_service` (R-C3-05 protection), the fund allocation becomes inherently safe.
|
|
1259
|
+
|
|
1260
|
+
4. **Use `confirmed: true` for Irreversible/Destructive Operations**: The MCP server enforces a two-phase confirmation for safety. You MUST add `"confirmed": true` to the `env` for:
|
|
1261
|
+
- Any operation using `replaceExistName: true` (unbinds existing names)
|
|
1262
|
+
- Any `publish: true` operation (irreversible lock on Machine/Service)
|
|
1263
|
+
Without `confirmed: true`, the server returns a `Confirmation required` warning and blocks the transaction.
|
|
1264
|
+
|
|
1265
|
+
5. **Use `no_cache: true` for Sequential Operations**: When performing multiple operations on the same object in sequence (especially Progress workflow advancement), always set `no_cache: true` in the `env` to ensure the SDK reads the latest on-chain state.
|
|
1266
|
+
|
|
1267
|
+
6. **Query Toolkit is Your Best Friend**: Use queries constantly to verify objects exist, check configurations, debug issues, and confirm state changes.
|
|
1268
|
+
|
|
1269
|
+
7. **Object IDs vs Names in Responses**: On-chain query responses return **object IDs** (e.g., `0x8202...`) for cross-object references like `buy_guard`, `machine`, `permission`. The `query_name` field in the response echoes back the original query input name. To resolve object IDs back to local mark names, use `query_toolkit` with `query_type: "local_names"`.
|
|
768
1270
|
|
|
769
|
-
|
|
1271
|
+
8. **Information Injection in Transaction Responses**: Mutation/creation operations return ALL objects affected by the transaction (including side effects like `TableItem_EntityLinker` and `TableItem_ProgressHistory`), not just the primary target. This is a deliberate design for transparency. The return order reflects the transaction's execution order.
|