@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.
@@ -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` account with sufficient WOW tokens
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
- ### Check Account Balance
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
- "address": "0x...",
45
- "balance": {
46
- "coinType": "0x2::wow::WOW",
47
- "totalBalance": "1000000000"
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
- **If no balance, use Faucet**:
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
- {"amount": 1000000000, "id": "0x..."},
71
- {"amount": 1000000000, "id": "0x..."},
72
- {"amount": 1000000000, "id": "0x..."}
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
- "type": "Permission",
112
- "object": "0x...",
113
- "version": "...",
114
- "change": "created"
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
- "type": "Guard",
171
- "object": "0x...",
172
- "version": "...",
173
- "change": "created"
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
- "type": "Machine",
246
- "object": "0x...",
247
- "version": "...",
248
- "change": "created"
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
- "type": "Service",
283
- "object": "0x...",
284
- "version": "...",
285
- "change": "created"
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
- "type": "Service",
314
- "object": "0x...",
315
- "version": "...",
316
- "change": "mutated"
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
- "type": "Service",
345
- "object": "0x...",
346
- "version": "...",
347
- "change": "mutated"
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": "three_body_buy_guard",
687
+ "guard": "three_body_allocator_guard",
369
688
  "sharing": [
370
689
  {
371
690
  "who": {
372
- "Signer": "signer"
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
- "type": "Service",
394
- "object": "0x...",
395
- "version": "...",
396
- "change": "mutated"
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
- "type": "Service",
438
- "object": "0x...",
439
- "version": "...",
440
- "change": "mutated"
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
- "type": "Service",
469
- "object": "0x...",
470
- "version": "...",
471
- "change": "mutated"
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
- "object": "three_body_signature_service",
495
- "type": "Service",
496
- "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.",
497
- "sales": [
498
- {
499
- "name": "Three-Body Book Signature",
500
- "stock": "100",
501
- "suspension": false,
502
- "price": "888",
503
- "wip": "",
504
- "wip_hash": ""
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": "Progress",
569
- "object": "three_body_progress",
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
- "object": "three_body_order",
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": "Allocation",
581
- "object": "three_body_allocation",
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
- ```json
624
- {
625
- "error": "Transaction resolution failed: MoveAbort in 8th command, abort code: 7 (Verify failed), in '0x2::passport::result_for_guard'"
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
- "type": "Progress",
663
- "object": "three_body_progress",
664
- "version": "...",
665
- "change": "mutated"
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
- "type": "Progress",
700
- "object": "three_body_progress",
701
- "version": "...",
702
- "change": "mutated"
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 (need Service address for verification logic)
764
- - Configure Service (add machine, buy_guard, order_allocators)
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. **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.
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
- 4. **Query Toolkit is Your Best Friend**: Use queries constantly to verify objects exist, check configurations, debug issues, and confirm state changes.
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.