@wowok/skills 3.0.3 → 3.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 +146 -122
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +223 -837
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +121 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +802 -0
- package/dist/installer.js.map +1 -0
- package/dist/skills.d.ts +5 -2
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +86 -62
- package/dist/skills.js.map +1 -1
- package/dist/targets.d.ts +94 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +421 -0
- package/dist/targets.js.map +1 -0
- package/dist/types.d.ts +5 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -32
- package/dist/types.js.map +1 -1
- package/package.json +7 -4
- package/scripts/install.js +21 -858
- package/wowok-arbitrator/SKILL.md +5 -12
- package/wowok-auditor/SKILL.md +5 -17
- package/wowok-collaborator/SKILL.md +5 -17
- package/wowok-governance/SKILL.md +95 -0
- package/wowok-machine/SKILL.md +5 -18
- package/wowok-market/SKILL.md +82 -0
- package/wowok-messenger/SKILL.md +5 -18
- package/wowok-onboard/SKILL.md +5 -22
- package/wowok-order/SKILL.md +5 -18
- package/wowok-output/SKILL.md +5 -10
- package/wowok-planner/SKILL.md +5 -19
- package/wowok-provider/SKILL.md +5 -17
- package/wowok-supplier/SKILL.md +5 -16
- package/examples/Insurance/Insurance.md +0 -1245
- package/examples/MyShop/MyShop.md +0 -2003
- package/examples/MyShop/myshop_machine_nodes.json +0 -93
- package/examples/MyShop_Advanced/MyShop_Advanced.md +0 -2874
- package/examples/ThreeBody_Signature/ThreeBody_Signature.md +0 -1831
- package/examples/Travel/Travel.md +0 -1849
- package/examples/Travel/calc-weather-timestamps.js +0 -12
|
@@ -1,1831 +0,0 @@
|
|
|
1
|
-
# Three-Body Author Signature Service
|
|
2
|
-
|
|
3
|
-
A complete example demonstrating how to create a service for book signing by the Three-Body author. This service allows customers to request a personalized message (up to 10 characters) on their book, signed by the author.
|
|
4
|
-
|
|
5
|
-
---
|
|
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 two `publish: true` steps in this doc: the Machine publish (**Step 3**), which irreversibly locks the workflow definition (`nodes`/`pairs`/`forwards`), and the Service publish (**Step 11**), which irreversibly locks the `machine` and `order_allocators` fields.
|
|
22
|
-
|
|
23
|
-
> **💡 Call Format**: All WoWok operations go through a single unified `wowok` tool. The AI calls `wowok({ tool: "<sub-tool>", data: {<params>} })`. If parameters don't match the schema, the response includes the correct schema for self-correction. See [Response Format](../../docs/response-format.md) for details.
|
|
24
|
-
>
|
|
25
|
-
> **📄 Expected Results**: The Expected Result envelopes below omit the optional `message` and `semantic` fields (human-readable summary / business semantic summary) for brevity — real responses may include them.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Overview
|
|
30
|
-
|
|
31
|
-
This example demonstrates:
|
|
32
|
-
|
|
33
|
-
**Service with Buy Guard**: The buy_guard allows you to define various conditions for users to purchase products, such as identity verification, completing KYC, or being on an allowlist. In this example, only the service creator (author) can purchase this service.
|
|
34
|
-
|
|
35
|
-
### Key Design Decisions
|
|
36
|
-
|
|
37
|
-
1. **Buy Guard Protection**: Only the author (`three_body_author`) can purchase this service, preventing unauthorized usage
|
|
38
|
-
2. **Simple Two-Node Workflow**: Clear progression from delivery to completion
|
|
39
|
-
3. **WIP Files Optional**: Can use WIP files or empty strings
|
|
40
|
-
4. **Fixed Price**: 888 WOW for the signature service
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Prerequisites
|
|
45
|
-
|
|
46
|
-
Before running this example, ensure you have:
|
|
47
|
-
|
|
48
|
-
1. **Account Setup**: The `three_body_author` and `three_body_customer` accounts with sufficient WOW tokens
|
|
49
|
-
2. **Minimum Balance**: At least 1000 WOW for gas fees and service creation
|
|
50
|
-
|
|
51
|
-
### Step 0a: Generate Accounts
|
|
52
|
-
|
|
53
|
-
If the accounts do not exist locally, generate them first.
|
|
54
|
-
|
|
55
|
-
**Request (generate author account)**:
|
|
56
|
-
```json
|
|
57
|
-
{
|
|
58
|
-
"tool": "account_operation",
|
|
59
|
-
"data": {
|
|
60
|
-
"gen": {
|
|
61
|
-
"name": "three_body_author",
|
|
62
|
-
"replaceExistName": true
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Request (generate customer account)**:
|
|
69
|
-
```json
|
|
70
|
-
{
|
|
71
|
-
"tool": "account_operation",
|
|
72
|
-
"data": {
|
|
73
|
-
"gen": {
|
|
74
|
-
"name": "three_body_customer",
|
|
75
|
-
"replaceExistName": true
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**Expected Result**:
|
|
82
|
-
```json
|
|
83
|
-
{
|
|
84
|
-
"result": {
|
|
85
|
-
"status": "success",
|
|
86
|
-
"data": {
|
|
87
|
-
"gen": {
|
|
88
|
-
"address": "0x...",
|
|
89
|
-
"name": "three_body_author"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
"schema": null
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Step 0b: Check Account Balance
|
|
98
|
-
|
|
99
|
-
**Request**:
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"tool": "query_toolkit",
|
|
103
|
-
"data": {
|
|
104
|
-
"query_type": "account_balance",
|
|
105
|
-
"name_or_address": "three_body_author",
|
|
106
|
-
"balance": true,
|
|
107
|
-
"network": "testnet"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Expected Result** (if account has balance):
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"result": {
|
|
116
|
-
"status": "success",
|
|
117
|
-
"data": {
|
|
118
|
-
"result": {
|
|
119
|
-
"query_type": "account_balance",
|
|
120
|
-
"result": {
|
|
121
|
-
"address": "0x...",
|
|
122
|
-
"name_or_address": "three_body_author",
|
|
123
|
-
"balance": {
|
|
124
|
-
"coinType": "0x2::wow::WOW",
|
|
125
|
-
"coinObjectCount": 3,
|
|
126
|
-
"totalBalance": "3000000000",
|
|
127
|
-
"lockedBalance": {},
|
|
128
|
-
"fundsInAddressBalance": "0"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
"schema": null
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Step 0c: Fund Accounts via Faucet (If No Balance)
|
|
139
|
-
|
|
140
|
-
**Request**:
|
|
141
|
-
```json
|
|
142
|
-
{
|
|
143
|
-
"tool": "account_operation",
|
|
144
|
-
"data": {
|
|
145
|
-
"faucet": {
|
|
146
|
-
"network": "testnet",
|
|
147
|
-
"name_or_address": "three_body_author"
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Expected Result**:
|
|
154
|
-
```json
|
|
155
|
-
{
|
|
156
|
-
"result": {
|
|
157
|
-
"status": "success",
|
|
158
|
-
"data": {
|
|
159
|
-
"faucet": {
|
|
160
|
-
"name_or_address": "three_body_author",
|
|
161
|
-
"result": [
|
|
162
|
-
{
|
|
163
|
-
"amount": 1000000000,
|
|
164
|
-
"id": "0x...",
|
|
165
|
-
"transferTxDigest": "..."
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
"amount": 1000000000,
|
|
169
|
-
"id": "0x...",
|
|
170
|
-
"transferTxDigest": "..."
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
"amount": 1000000000,
|
|
174
|
-
"id": "0x...",
|
|
175
|
-
"transferTxDigest": "..."
|
|
176
|
-
}
|
|
177
|
-
],
|
|
178
|
-
"network": "testnet"
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
"schema": null
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
> **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.
|
|
187
|
-
>
|
|
188
|
-
> **Amount Note**: Each faucet request distributes 3 WOW (3 × 1 WOW; WOW has 9 decimals, so 1 WOW = 1,000,000,000 smallest units). Since the sale price in this example is 888 WOW, repeat the faucet request until each account holds ≥ 1000 WOW (price + gas).
|
|
189
|
-
|
|
190
|
-
> **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.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Step 1: Create Permission Object
|
|
195
|
-
|
|
196
|
-
Create a Permission object to manage the service.
|
|
197
|
-
|
|
198
|
-
**Request**:
|
|
199
|
-
```json
|
|
200
|
-
{
|
|
201
|
-
"tool": "onchain_operations",
|
|
202
|
-
"data": {
|
|
203
|
-
"operation_type": "permission",
|
|
204
|
-
"data": {
|
|
205
|
-
"object": {
|
|
206
|
-
"name": "three_body_permission",
|
|
207
|
-
"replaceExistName": true
|
|
208
|
-
},
|
|
209
|
-
"description": "Permission for Three-Body Signature Service",
|
|
210
|
-
"table": {
|
|
211
|
-
"op": "add perm by entity",
|
|
212
|
-
"entity": {"name_or_address": "three_body_author"},
|
|
213
|
-
"index": [1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 306]
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
"env": {
|
|
217
|
-
"account": "three_body_author",
|
|
218
|
-
"network": "testnet",
|
|
219
|
-
"confirmed": true
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
> **Permission Index Explanation**:
|
|
226
|
-
> - **Admin bypass (why this single-account flow works)**: The Permission creator (`three_body_author`) automatically becomes an **admin** of the Permission object, and admins bypass per-index checks — the SDK's permission check only looks up granted indexes when the caller is NOT an admin. So the author can execute every step below even though the granted index list does not enumerate every built-in index used.
|
|
227
|
-
> - **`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. These grants matter for non-admin operators: a Machine forward checks its `permissionIndex` against the operator's granted indexes (or admin status).
|
|
228
|
-
> - **`306`**: The built-in `SERVICE_MACHINE` permission (Service module permission index `306`), which authorizes binding a Machine to a Service (used in Step 5, Configure Machine). It is granted here defensively — the author does not strictly need it thanks to the admin bypass above, but a non-admin operator would.
|
|
229
|
-
> - **Full built-in index list a NON-admin operator would need for this example**: Service ops — `305` (SERVICE_SALES), `306` (SERVICE_MACHINE), `307` (SERVICE_BUY_GUARD), `309` (SERVICE_CUSTOMER_INFO_REQUIRED), `310` (SERVICE_PAUSE), `311` (SERVICE_PUBLISH), `313` (SERVICE_ORDER_ALLOCATOR); Machine ops — `200` (MACHINE_NEW), `201` (MACHINE_DESCRIPTION), `205` (MACHINE_PUBLISH), `206` (MACHINE_NODE); Treasury — `250` (TREASURY_NEW), `251` (TREASURY_DESCRIPTION).
|
|
230
|
-
>
|
|
231
|
-
> See `BuiltinPermissionIndex` in `ts-sdk/packages/wowok/src/w/call/permission.ts` for the full list of built-in permission indexes.
|
|
232
|
-
|
|
233
|
-
> **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`.
|
|
234
|
-
|
|
235
|
-
**Expected Result**:
|
|
236
|
-
```json
|
|
237
|
-
{
|
|
238
|
-
"result": {
|
|
239
|
-
"status": "success",
|
|
240
|
-
"data": {
|
|
241
|
-
"result": {
|
|
242
|
-
"type": "transaction",
|
|
243
|
-
"objectChanges": [
|
|
244
|
-
{
|
|
245
|
-
"type": "Permission",
|
|
246
|
-
"type_raw": "0x2::permission::Permission",
|
|
247
|
-
"object": "0x...",
|
|
248
|
-
"version": "...",
|
|
249
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
250
|
-
"change": "created"
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
"type": "TableItem_PermissionPerm",
|
|
254
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::parent_linked_table::Node<address, vector<u16>>>",
|
|
255
|
-
"object": "0x...",
|
|
256
|
-
"version": "...",
|
|
257
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
258
|
-
"change": "created"
|
|
259
|
-
}
|
|
260
|
-
]
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
"schema": null
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
## Step 2: Create Buy Guard
|
|
271
|
-
|
|
272
|
-
Create a Guard that verifies the buyer is the service creator (author). This ensures only the author can purchase this service.
|
|
273
|
-
|
|
274
|
-
**Request**:
|
|
275
|
-
```json
|
|
276
|
-
{
|
|
277
|
-
"tool": "onchain_operations",
|
|
278
|
-
"data": {
|
|
279
|
-
"operation_type": "guard",
|
|
280
|
-
"data": {
|
|
281
|
-
"namedNew": {
|
|
282
|
-
"name": "three_body_buy_guard",
|
|
283
|
-
"tags": ["signature", "book", "buy-guard", "level1-strict"],
|
|
284
|
-
"replaceExistName": true
|
|
285
|
-
},
|
|
286
|
-
"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.",
|
|
287
|
-
"table": [
|
|
288
|
-
{
|
|
289
|
-
"identifier": 0,
|
|
290
|
-
"b_submission": false,
|
|
291
|
-
"value_type": "Address",
|
|
292
|
-
"value": "three_body_author",
|
|
293
|
-
"name": "Author address"
|
|
294
|
-
}
|
|
295
|
-
],
|
|
296
|
-
"root": {
|
|
297
|
-
"type": "logic_equal",
|
|
298
|
-
"nodes": [
|
|
299
|
-
{
|
|
300
|
-
"type": "context",
|
|
301
|
-
"context": "Signer"
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
"type": "identifier",
|
|
305
|
-
"identifier": 0
|
|
306
|
-
}
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
"env": {
|
|
311
|
-
"account": "three_body_author",
|
|
312
|
-
"network": "testnet",
|
|
313
|
-
"confirmed": true
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
> **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).
|
|
320
|
-
|
|
321
|
-
> **⚠️ 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.
|
|
322
|
-
|
|
323
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
324
|
-
|
|
325
|
-
**Expected Result**:
|
|
326
|
-
```json
|
|
327
|
-
{
|
|
328
|
-
"result": {
|
|
329
|
-
"status": "success",
|
|
330
|
-
"data": {
|
|
331
|
-
"result": {
|
|
332
|
-
"type": "transaction",
|
|
333
|
-
"objectChanges": [
|
|
334
|
-
{
|
|
335
|
-
"type": "Guard",
|
|
336
|
-
"type_raw": "0x2::guard::Guard",
|
|
337
|
-
"object": "0x...",
|
|
338
|
-
"version": "...",
|
|
339
|
-
"owner": "Immutable",
|
|
340
|
-
"change": "created"
|
|
341
|
-
}
|
|
342
|
-
]
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
"schema": null
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## Step 3: Create Machine with Workflow Nodes
|
|
353
|
-
|
|
354
|
-
Create a Machine to define the service workflow: Book Delivery → Signature Completion.
|
|
355
|
-
|
|
356
|
-
### Create Machine
|
|
357
|
-
|
|
358
|
-
**Request**:
|
|
359
|
-
```json
|
|
360
|
-
{
|
|
361
|
-
"tool": "onchain_operations",
|
|
362
|
-
"data": {
|
|
363
|
-
"operation_type": "machine",
|
|
364
|
-
"data": {
|
|
365
|
-
"object": {
|
|
366
|
-
"name": "three_body_machine",
|
|
367
|
-
"permission": "three_body_permission",
|
|
368
|
-
"replaceExistName": true
|
|
369
|
-
},
|
|
370
|
-
"description": "Three-Body signature service workflow: Book Delivery -> Signature Completion",
|
|
371
|
-
"node": {
|
|
372
|
-
"op": "add",
|
|
373
|
-
"nodes": [
|
|
374
|
-
{
|
|
375
|
-
"name": "Book Delivered",
|
|
376
|
-
"pairs": [
|
|
377
|
-
{
|
|
378
|
-
"prev_node": "",
|
|
379
|
-
"threshold": 0,
|
|
380
|
-
"forwards": [
|
|
381
|
-
{
|
|
382
|
-
"name": "Confirm Delivery",
|
|
383
|
-
"permissionIndex": 1000,
|
|
384
|
-
"weight": 1
|
|
385
|
-
}
|
|
386
|
-
]
|
|
387
|
-
}
|
|
388
|
-
]
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
"name": "Signature Completed",
|
|
392
|
-
"pairs": [
|
|
393
|
-
{
|
|
394
|
-
"prev_node": "Book Delivered",
|
|
395
|
-
"threshold": 1,
|
|
396
|
-
"forwards": [
|
|
397
|
-
{
|
|
398
|
-
"name": "Complete Signature",
|
|
399
|
-
"permissionIndex": 1001,
|
|
400
|
-
"weight": 1
|
|
401
|
-
}
|
|
402
|
-
]
|
|
403
|
-
}
|
|
404
|
-
]
|
|
405
|
-
}
|
|
406
|
-
]
|
|
407
|
-
},
|
|
408
|
-
"publish": true
|
|
409
|
-
},
|
|
410
|
-
"env": {
|
|
411
|
-
"account": "three_body_author",
|
|
412
|
-
"network": "testnet",
|
|
413
|
-
"confirmed": true
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt. Additionally, `publish: true` on the Machine is an irreversible operation.
|
|
420
|
-
|
|
421
|
-
**Expected Result**:
|
|
422
|
-
```json
|
|
423
|
-
{
|
|
424
|
-
"result": {
|
|
425
|
-
"status": "success",
|
|
426
|
-
"data": {
|
|
427
|
-
"result": {
|
|
428
|
-
"type": "transaction",
|
|
429
|
-
"objectChanges": [
|
|
430
|
-
{
|
|
431
|
-
"type": "TableItem_MachineNode",
|
|
432
|
-
"type_raw": "0x2::dynamic_field::Field<0x1::string::String, 0x2::parent_linked_table::Node<0x1::string::String, vector<0x2::machine::NodePair>>>",
|
|
433
|
-
"object": "0x...",
|
|
434
|
-
"version": "...",
|
|
435
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
436
|
-
"change": "created"
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
"type": "TableItem_MachineNode",
|
|
440
|
-
"type_raw": "0x2::dynamic_field::Field<0x1::string::String, 0x2::parent_linked_table::Node<0x1::string::String, vector<0x2::machine::NodePair>>>",
|
|
441
|
-
"object": "0x...",
|
|
442
|
-
"version": "...",
|
|
443
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
444
|
-
"change": "created"
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
"type": "Machine",
|
|
448
|
-
"type_raw": "0x2::machine::Machine",
|
|
449
|
-
"object": "0x...",
|
|
450
|
-
"version": "...",
|
|
451
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
452
|
-
"change": "created"
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
"type": "TableItem_EntityLinker",
|
|
456
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
457
|
-
"object": "0x...",
|
|
458
|
-
"version": "...",
|
|
459
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
460
|
-
"change": "created"
|
|
461
|
-
}
|
|
462
|
-
]
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
"schema": null
|
|
467
|
-
}
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
---
|
|
471
|
-
|
|
472
|
-
## Step 4: Create Service (Unpublished)
|
|
473
|
-
|
|
474
|
-
Create the Three-Body signature service without publishing. The Service must be unpublished when binding the Machine.
|
|
475
|
-
|
|
476
|
-
**Request**:
|
|
477
|
-
```json
|
|
478
|
-
{
|
|
479
|
-
"tool": "onchain_operations",
|
|
480
|
-
"data": {
|
|
481
|
-
"operation_type": "service",
|
|
482
|
-
"data": {
|
|
483
|
-
"object": {
|
|
484
|
-
"name": "three_body_signature_service",
|
|
485
|
-
"type_parameter": "0x2::wow::WOW",
|
|
486
|
-
"permission": "three_body_permission",
|
|
487
|
-
"replaceExistName": true
|
|
488
|
-
},
|
|
489
|
-
"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 WOW.",
|
|
490
|
-
"publish": false
|
|
491
|
-
},
|
|
492
|
-
"env": {
|
|
493
|
-
"account": "three_body_author",
|
|
494
|
-
"network": "testnet",
|
|
495
|
-
"confirmed": true
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
502
|
-
|
|
503
|
-
**Expected Result**:
|
|
504
|
-
```json
|
|
505
|
-
{
|
|
506
|
-
"result": {
|
|
507
|
-
"status": "success",
|
|
508
|
-
"data": {
|
|
509
|
-
"result": {
|
|
510
|
-
"type": "transaction",
|
|
511
|
-
"objectChanges": [
|
|
512
|
-
{
|
|
513
|
-
"type": "TableItem_EntityLinker",
|
|
514
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
515
|
-
"object": "0x...",
|
|
516
|
-
"version": "...",
|
|
517
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
518
|
-
"change": "mutated"
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
"type": "Service",
|
|
522
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
523
|
-
"object": "0x...",
|
|
524
|
-
"version": "...",
|
|
525
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
526
|
-
"change": "created"
|
|
527
|
-
}
|
|
528
|
-
]
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
"schema": null
|
|
533
|
-
}
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
---
|
|
537
|
-
|
|
538
|
-
## Step 5: Configure Machine
|
|
539
|
-
|
|
540
|
-
Bind the published Machine to the Service. **Important**: The Service must be unpublished when binding the Machine.
|
|
541
|
-
|
|
542
|
-
**Request**:
|
|
543
|
-
```json
|
|
544
|
-
{
|
|
545
|
-
"tool": "onchain_operations",
|
|
546
|
-
"data": {
|
|
547
|
-
"operation_type": "service",
|
|
548
|
-
"data": {
|
|
549
|
-
"object": "three_body_signature_service",
|
|
550
|
-
"machine": "three_body_machine"
|
|
551
|
-
},
|
|
552
|
-
"env": {
|
|
553
|
-
"account": "three_body_author",
|
|
554
|
-
"network": "testnet"
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
**Expected Result**:
|
|
561
|
-
```json
|
|
562
|
-
{
|
|
563
|
-
"result": {
|
|
564
|
-
"status": "success",
|
|
565
|
-
"data": {
|
|
566
|
-
"result": {
|
|
567
|
-
"type": "transaction",
|
|
568
|
-
"objectChanges": [
|
|
569
|
-
{
|
|
570
|
-
"type": "Service",
|
|
571
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
572
|
-
"object": "0x...",
|
|
573
|
-
"version": "...",
|
|
574
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
575
|
-
"change": "mutated"
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
"type": "TableItem_EntityLinker",
|
|
579
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
580
|
-
"object": "0x...",
|
|
581
|
-
"version": "...",
|
|
582
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
583
|
-
"change": "created"
|
|
584
|
-
}
|
|
585
|
-
]
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
"schema": null
|
|
590
|
-
}
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
---
|
|
594
|
-
|
|
595
|
-
## Step 6: Set Buy Guard
|
|
596
|
-
|
|
597
|
-
Configure the Buy Guard to restrict purchases to the author only.
|
|
598
|
-
|
|
599
|
-
**Request**:
|
|
600
|
-
```json
|
|
601
|
-
{
|
|
602
|
-
"tool": "onchain_operations",
|
|
603
|
-
"data": {
|
|
604
|
-
"operation_type": "service",
|
|
605
|
-
"data": {
|
|
606
|
-
"object": "three_body_signature_service",
|
|
607
|
-
"buy_guard": "three_body_buy_guard"
|
|
608
|
-
},
|
|
609
|
-
"env": {
|
|
610
|
-
"account": "three_body_author",
|
|
611
|
-
"network": "testnet"
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
**Expected Result**:
|
|
618
|
-
```json
|
|
619
|
-
{
|
|
620
|
-
"result": {
|
|
621
|
-
"status": "success",
|
|
622
|
-
"data": {
|
|
623
|
-
"result": {
|
|
624
|
-
"type": "transaction",
|
|
625
|
-
"objectChanges": [
|
|
626
|
-
{
|
|
627
|
-
"type": "Service",
|
|
628
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
629
|
-
"object": "0x...",
|
|
630
|
-
"version": "...",
|
|
631
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
632
|
-
"change": "mutated"
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
"type": "TableItem_EntityLinker",
|
|
636
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
637
|
-
"object": "0x...",
|
|
638
|
-
"version": "...",
|
|
639
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
640
|
-
"change": "created"
|
|
641
|
-
}
|
|
642
|
-
]
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
},
|
|
646
|
-
"schema": null
|
|
647
|
-
}
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
---
|
|
651
|
-
|
|
652
|
-
## Step 7: Create Treasury Object
|
|
653
|
-
|
|
654
|
-
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.
|
|
655
|
-
|
|
656
|
-
**Request**:
|
|
657
|
-
```json
|
|
658
|
-
{
|
|
659
|
-
"tool": "onchain_operations",
|
|
660
|
-
"data": {
|
|
661
|
-
"operation_type": "treasury",
|
|
662
|
-
"data": {
|
|
663
|
-
"object": {
|
|
664
|
-
"name": "three_body_treasury",
|
|
665
|
-
"type_parameter": "0x2::wow::WOW",
|
|
666
|
-
"permission": "three_body_permission",
|
|
667
|
-
"replaceExistName": true
|
|
668
|
-
},
|
|
669
|
-
"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."
|
|
670
|
-
},
|
|
671
|
-
"env": {
|
|
672
|
-
"account": "three_body_author",
|
|
673
|
-
"network": "testnet",
|
|
674
|
-
"confirmed": true
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
> **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:
|
|
681
|
-
> 1. **Aggregates public funds** for the author's operational distribution and accounting
|
|
682
|
-
> 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
|
|
683
|
-
> 3. **Uses permission consistency** — Treasury and Service share `three_body_permission`, ensuring unified governance
|
|
684
|
-
|
|
685
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
686
|
-
|
|
687
|
-
**Expected Result**:
|
|
688
|
-
```json
|
|
689
|
-
{
|
|
690
|
-
"result": {
|
|
691
|
-
"status": "success",
|
|
692
|
-
"data": {
|
|
693
|
-
"result": {
|
|
694
|
-
"type": "transaction",
|
|
695
|
-
"objectChanges": [
|
|
696
|
-
{
|
|
697
|
-
"type": "Treasury",
|
|
698
|
-
"type_raw": "0x2::treasury::Treasury<0x2::wow::WOW>",
|
|
699
|
-
"object": "0x...",
|
|
700
|
-
"version": "...",
|
|
701
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
702
|
-
"change": "created"
|
|
703
|
-
},
|
|
704
|
-
{
|
|
705
|
-
"type": "TableItem_EntityLinker",
|
|
706
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
707
|
-
"object": "0x...",
|
|
708
|
-
"version": "...",
|
|
709
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
710
|
-
"change": "created"
|
|
711
|
-
}
|
|
712
|
-
]
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
},
|
|
716
|
-
"schema": null
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
---
|
|
721
|
-
|
|
722
|
-
## Step 8: Create Contact Object (Customer-Service Channel)
|
|
723
|
-
|
|
724
|
-
Create a Contact object to serve as the Service's encrypted customer-service channel. **Whenever `customer_required` is set (e.g. phone/email/shipping address), a Contact MUST be bound via the `um` field** — the customer's private information is delivered exclusively through end-to-end encrypted Messenger, which routes messages to the Contact bound as `um`. Without `um`, there is no channel to receive the customer's privacy-sensitive input and the SDK blocks the call with an invalid parameter error. The Contact uses the same Permission as the Service (`three_body_permission`) for unified governance.
|
|
725
|
-
|
|
726
|
-
**Request**:
|
|
727
|
-
```json
|
|
728
|
-
{
|
|
729
|
-
"tool": "onchain_operations",
|
|
730
|
-
"data": {
|
|
731
|
-
"operation_type": "contact",
|
|
732
|
-
"data": {
|
|
733
|
-
"object": {
|
|
734
|
-
"name": "three_body_contact",
|
|
735
|
-
"permission": "three_body_permission",
|
|
736
|
-
"replaceExistName": true
|
|
737
|
-
},
|
|
738
|
-
"description": "Contact for Three-Body signature service customer info — end-to-end encrypted Messenger channel for delivery of phone/email/shipping_address collected via customer_required."
|
|
739
|
-
},
|
|
740
|
-
"env": {
|
|
741
|
-
"account": "three_body_author",
|
|
742
|
-
"network": "testnet",
|
|
743
|
-
"confirmed": true
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
> **Why a Contact is required with customer_required (SDK-enforced hard linkage)**:
|
|
750
|
-
> - `customer_required` tells the order flow which private-info labels to collect from the buyer (phone, email, shipping_address, etc.)
|
|
751
|
-
> - Those labels are delivered to the merchant ONLY through the end-to-end encrypted Messenger protocol
|
|
752
|
-
> - Messenger routes messages to the Service's bound Contact object (the `um` field)
|
|
753
|
-
> - Without `um`, the collected private information would be silently dropped — there is no other delivery path
|
|
754
|
-
> - The SDK's `checkCustomerRequiredNeedsUm()` validator in `service.ts` (L1226) runs on EVERY service operation that touches `customer_required` AND on `publish: true`, so the constraint cannot be bypassed by splitting into two calls
|
|
755
|
-
|
|
756
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
757
|
-
|
|
758
|
-
**Expected Result**:
|
|
759
|
-
```json
|
|
760
|
-
{
|
|
761
|
-
"result": {
|
|
762
|
-
"status": "success",
|
|
763
|
-
"data": {
|
|
764
|
-
"result": {
|
|
765
|
-
"type": "transaction",
|
|
766
|
-
"objectChanges": [
|
|
767
|
-
{
|
|
768
|
-
"type": "Contact",
|
|
769
|
-
"type_raw": "0x2::contact::Contact",
|
|
770
|
-
"object": "0x...",
|
|
771
|
-
"version": "...",
|
|
772
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
773
|
-
"change": "created"
|
|
774
|
-
}
|
|
775
|
-
]
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
},
|
|
779
|
-
"schema": null
|
|
780
|
-
}
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
---
|
|
784
|
-
|
|
785
|
-
## Step 9: Create Allocator Guard
|
|
786
|
-
|
|
787
|
-
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.
|
|
788
|
-
|
|
789
|
-
**Guard Logic**:
|
|
790
|
-
```
|
|
791
|
-
order.service == three_body_signature_service
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
**Request**:
|
|
795
|
-
```json
|
|
796
|
-
{
|
|
797
|
-
"tool": "onchain_operations",
|
|
798
|
-
"data": {
|
|
799
|
-
"operation_type": "guard",
|
|
800
|
-
"data": {
|
|
801
|
-
"namedNew": {
|
|
802
|
-
"name": "three_body_allocator_guard",
|
|
803
|
-
"tags": ["signature", "book", "allocator", "level3-scene-combined"],
|
|
804
|
-
"replaceExistName": true
|
|
805
|
-
},
|
|
806
|
-
"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).",
|
|
807
|
-
"table": [
|
|
808
|
-
{
|
|
809
|
-
"identifier": 0,
|
|
810
|
-
"b_submission": true,
|
|
811
|
-
"value_type": "Address",
|
|
812
|
-
"name": "Order ID (submitted at runtime)"
|
|
813
|
-
},
|
|
814
|
-
{
|
|
815
|
-
"identifier": 1,
|
|
816
|
-
"b_submission": false,
|
|
817
|
-
"value_type": "Address",
|
|
818
|
-
"value": "three_body_signature_service",
|
|
819
|
-
"name": "Service address (this service)"
|
|
820
|
-
}
|
|
821
|
-
],
|
|
822
|
-
"root": {
|
|
823
|
-
"type": "logic_equal",
|
|
824
|
-
"nodes": [
|
|
825
|
-
{
|
|
826
|
-
"type": "query",
|
|
827
|
-
"query": "order.service",
|
|
828
|
-
"object": {"identifier": 0},
|
|
829
|
-
"parameters": []
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
"type": "identifier",
|
|
833
|
-
"identifier": 1
|
|
834
|
-
}
|
|
835
|
-
]
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
"env": {
|
|
839
|
-
"account": "three_body_author",
|
|
840
|
-
"network": "testnet",
|
|
841
|
-
"confirmed": true
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
**Guard Explanation (Service Ownership Check — Level 3 Scene-Combined):**
|
|
848
|
-
- **Table Item 0**: Order address (submitted at runtime, `b_submission: true`) — the order being allocated
|
|
849
|
-
- **Table Item 1**: Constant address `three_body_signature_service` (this service's on-chain address)
|
|
850
|
-
- **Root**: `logic_equal[query("order.service"), identifier[1]]` — verifies the submitted Order's `service` field equals `three_body_signature_service`
|
|
851
|
-
|
|
852
|
-
> **Risk Elimination (R-C3-05 + R-C3-06) — Level 3 Scene-Combined Design**:
|
|
853
|
-
> - **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`.
|
|
854
|
-
> - **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.
|
|
855
|
-
|
|
856
|
-
> **Important**: `env.confirmed: true` is required because `replaceExistName: true` triggers a confirmation prompt.
|
|
857
|
-
|
|
858
|
-
**Expected Result**:
|
|
859
|
-
```json
|
|
860
|
-
{
|
|
861
|
-
"result": {
|
|
862
|
-
"status": "success",
|
|
863
|
-
"data": {
|
|
864
|
-
"result": {
|
|
865
|
-
"type": "transaction",
|
|
866
|
-
"objectChanges": [
|
|
867
|
-
{
|
|
868
|
-
"type": "Guard",
|
|
869
|
-
"type_raw": "0x2::guard::Guard",
|
|
870
|
-
"object": "0x...",
|
|
871
|
-
"version": "...",
|
|
872
|
-
"owner": "Immutable",
|
|
873
|
-
"change": "created"
|
|
874
|
-
}
|
|
875
|
-
]
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
"schema": null
|
|
880
|
-
}
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
---
|
|
884
|
-
|
|
885
|
-
## Step 10: Configure Order Allocators
|
|
886
|
-
|
|
887
|
-
Set up fund allocation: 100% to the author's Treasury upon order completion. Also bind the Contact (Step 8) as `um` to enable the `customer_required` private-info delivery channel — the SDK blocks `customer_required` without a matching `um` (see Step 8 for the rationale).
|
|
888
|
-
|
|
889
|
-
**Request**:
|
|
890
|
-
```json
|
|
891
|
-
{
|
|
892
|
-
"tool": "onchain_operations",
|
|
893
|
-
"data": {
|
|
894
|
-
"operation_type": "service",
|
|
895
|
-
"data": {
|
|
896
|
-
"object": "three_body_signature_service",
|
|
897
|
-
"order_allocators": {
|
|
898
|
-
"description": "Three-Body signature service fund allocation - 100% to author Treasury",
|
|
899
|
-
"threshold": 0,
|
|
900
|
-
"allocators": [
|
|
901
|
-
{
|
|
902
|
-
"guard": "three_body_allocator_guard",
|
|
903
|
-
"sharing": [
|
|
904
|
-
{
|
|
905
|
-
"who": {
|
|
906
|
-
"Entity": {"name_or_address": "three_body_treasury"}
|
|
907
|
-
},
|
|
908
|
-
"sharing": 10000,
|
|
909
|
-
"mode": "Rate"
|
|
910
|
-
}
|
|
911
|
-
]
|
|
912
|
-
}
|
|
913
|
-
]
|
|
914
|
-
},
|
|
915
|
-
"customer_required": ["phone", "email", "shipping_address"],
|
|
916
|
-
"um": "three_body_contact"
|
|
917
|
-
},
|
|
918
|
-
"env": {
|
|
919
|
-
"account": "three_body_author",
|
|
920
|
-
"network": "testnet"
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
> **⚠️ Risk Elimination — Why this configuration is safe**:
|
|
927
|
-
> - **R-C3-05 (Cross-service theft)**: Eliminated by `three_body_allocator_guard` (Step 9), which verifies `order.service == three_body_signature_service` before allocation proceeds.
|
|
928
|
-
> - **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.
|
|
929
|
-
> - **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.
|
|
930
|
-
> - **SDK-enforced constraint (customer_required ⟶ um)**: `"customer_required"` is set alongside `"um": "three_body_contact"` in the SAME call. The SDK validator `checkCustomerRequiredNeedsUm()` in `service.ts` L1226 also runs on publish (L314), so splitting into two calls (e.g. `customer_required` now, `um` later) would still fail at publish time — they are both required before the Service goes live.
|
|
931
|
-
|
|
932
|
-
**Expected Result**:
|
|
933
|
-
```json
|
|
934
|
-
{
|
|
935
|
-
"result": {
|
|
936
|
-
"status": "success",
|
|
937
|
-
"data": {
|
|
938
|
-
"result": {
|
|
939
|
-
"type": "transaction",
|
|
940
|
-
"objectChanges": [
|
|
941
|
-
{
|
|
942
|
-
"type": "Service",
|
|
943
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
944
|
-
"object": "0x...",
|
|
945
|
-
"version": "...",
|
|
946
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
947
|
-
"change": "mutated"
|
|
948
|
-
}
|
|
949
|
-
]
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
"schema": null
|
|
954
|
-
}
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
---
|
|
958
|
-
|
|
959
|
-
## Step 11: Add Sales and Publish Service
|
|
960
|
-
|
|
961
|
-
Add sales items and publish the service to make it available for orders.
|
|
962
|
-
|
|
963
|
-
**Request**:
|
|
964
|
-
```json
|
|
965
|
-
{
|
|
966
|
-
"tool": "onchain_operations",
|
|
967
|
-
"data": {
|
|
968
|
-
"operation_type": "service",
|
|
969
|
-
"data": {
|
|
970
|
-
"object": "three_body_signature_service",
|
|
971
|
-
"sales": {
|
|
972
|
-
"op": "add",
|
|
973
|
-
"sales": [
|
|
974
|
-
{
|
|
975
|
-
"name": "Three-Body Book Signature",
|
|
976
|
-
"price": "888WOW",
|
|
977
|
-
"stock": 100,
|
|
978
|
-
"suspension": false,
|
|
979
|
-
"wip": "",
|
|
980
|
-
"wip_hash": ""
|
|
981
|
-
}
|
|
982
|
-
]
|
|
983
|
-
},
|
|
984
|
-
"publish": true
|
|
985
|
-
},
|
|
986
|
-
"env": {
|
|
987
|
-
"account": "three_body_author",
|
|
988
|
-
"network": "testnet",
|
|
989
|
-
"confirmed": true
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
> **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.
|
|
996
|
-
|
|
997
|
-
**Expected Result**:
|
|
998
|
-
```json
|
|
999
|
-
{
|
|
1000
|
-
"result": {
|
|
1001
|
-
"status": "success",
|
|
1002
|
-
"data": {
|
|
1003
|
-
"result": {
|
|
1004
|
-
"type": "transaction",
|
|
1005
|
-
"objectChanges": [
|
|
1006
|
-
{
|
|
1007
|
-
"type": "Service",
|
|
1008
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
1009
|
-
"object": "0x...",
|
|
1010
|
-
"version": "...",
|
|
1011
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1012
|
-
"change": "mutated"
|
|
1013
|
-
}
|
|
1014
|
-
]
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
},
|
|
1018
|
-
"schema": null
|
|
1019
|
-
}
|
|
1020
|
-
```
|
|
1021
|
-
|
|
1022
|
-
---
|
|
1023
|
-
|
|
1024
|
-
## Step 12: Unpause Service
|
|
1025
|
-
|
|
1026
|
-
Unpause the service to allow order creation.
|
|
1027
|
-
|
|
1028
|
-
**Request**:
|
|
1029
|
-
```json
|
|
1030
|
-
{
|
|
1031
|
-
"tool": "onchain_operations",
|
|
1032
|
-
"data": {
|
|
1033
|
-
"operation_type": "service",
|
|
1034
|
-
"data": {
|
|
1035
|
-
"object": "three_body_signature_service",
|
|
1036
|
-
"pause": false
|
|
1037
|
-
},
|
|
1038
|
-
"env": {
|
|
1039
|
-
"account": "three_body_author",
|
|
1040
|
-
"network": "testnet"
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
```
|
|
1045
|
-
|
|
1046
|
-
**Expected Result**:
|
|
1047
|
-
```json
|
|
1048
|
-
{
|
|
1049
|
-
"result": {
|
|
1050
|
-
"status": "success",
|
|
1051
|
-
"data": {
|
|
1052
|
-
"result": {
|
|
1053
|
-
"type": "transaction",
|
|
1054
|
-
"objectChanges": [
|
|
1055
|
-
{
|
|
1056
|
-
"type": "Service",
|
|
1057
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
1058
|
-
"object": "0x...",
|
|
1059
|
-
"version": "...",
|
|
1060
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1061
|
-
"change": "mutated"
|
|
1062
|
-
}
|
|
1063
|
-
]
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
},
|
|
1067
|
-
"schema": null
|
|
1068
|
-
}
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
## Step 13: Verify Service Configuration
|
|
1074
|
-
|
|
1075
|
-
Query the service to verify all configurations.
|
|
1076
|
-
|
|
1077
|
-
**Request**:
|
|
1078
|
-
```json
|
|
1079
|
-
{
|
|
1080
|
-
"tool": "query_toolkit",
|
|
1081
|
-
"data": {
|
|
1082
|
-
"query_type": "onchain_objects",
|
|
1083
|
-
"objects": ["three_body_signature_service"],
|
|
1084
|
-
"no_cache": true,
|
|
1085
|
-
"network": "testnet"
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
**Expected Result**:
|
|
1091
|
-
```json
|
|
1092
|
-
{
|
|
1093
|
-
"result": {
|
|
1094
|
-
"status": "success",
|
|
1095
|
-
"data": {
|
|
1096
|
-
"result": {
|
|
1097
|
-
"query_type": "onchain_objects",
|
|
1098
|
-
"result": {
|
|
1099
|
-
"objects": [
|
|
1100
|
-
{
|
|
1101
|
-
"object": "0x...",
|
|
1102
|
-
"type": "Service",
|
|
1103
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
1104
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1105
|
-
"version": "...",
|
|
1106
|
-
"previousTransaction": "...",
|
|
1107
|
-
"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 WOW.",
|
|
1108
|
-
"location": "",
|
|
1109
|
-
"sales": [
|
|
1110
|
-
{
|
|
1111
|
-
"name": "Three-Body Book Signature",
|
|
1112
|
-
"stock": "100",
|
|
1113
|
-
"suspension": false,
|
|
1114
|
-
"price": "888000000000",
|
|
1115
|
-
"wip": "",
|
|
1116
|
-
"wip_hash": ""
|
|
1117
|
-
}
|
|
1118
|
-
],
|
|
1119
|
-
"repositories": [],
|
|
1120
|
-
"buy_guard": "0x...",
|
|
1121
|
-
"machine": "0x...",
|
|
1122
|
-
"bPublished": true,
|
|
1123
|
-
"bPaused": false,
|
|
1124
|
-
"customer_required": ["phone", "email", "shipping_address"],
|
|
1125
|
-
"arbitrations": [],
|
|
1126
|
-
"compensation_fund": "0",
|
|
1127
|
-
"paused_time": null,
|
|
1128
|
-
"setting_lock_duration": "2592000000",
|
|
1129
|
-
"order_allocators": {
|
|
1130
|
-
"description": "Three-Body signature service fund allocation - 100% to author Treasury",
|
|
1131
|
-
"threshold": "0",
|
|
1132
|
-
"allocators": [
|
|
1133
|
-
{
|
|
1134
|
-
"guard": "0x...",
|
|
1135
|
-
"sharing": [
|
|
1136
|
-
{
|
|
1137
|
-
"who": {"Entity": "0x..."},
|
|
1138
|
-
"sharing": "10000",
|
|
1139
|
-
"mode": 1
|
|
1140
|
-
}
|
|
1141
|
-
],
|
|
1142
|
-
"fix": "0",
|
|
1143
|
-
"max": null
|
|
1144
|
-
}
|
|
1145
|
-
]
|
|
1146
|
-
},
|
|
1147
|
-
"rewards": [],
|
|
1148
|
-
"um": "0x...",
|
|
1149
|
-
"permission": "0x...",
|
|
1150
|
-
"cache_expire": 1234567890,
|
|
1151
|
-
"query_name": "three_body_signature_service"
|
|
1152
|
-
}
|
|
1153
|
-
]
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
},
|
|
1158
|
-
"schema": null
|
|
1159
|
-
}
|
|
1160
|
-
```
|
|
1161
|
-
|
|
1162
|
-
> **Field Reference**:
|
|
1163
|
-
> - **`buy_guard`**, **`machine`**, **`permission`**, **`um`**: 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.
|
|
1164
|
-
> - **`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.
|
|
1165
|
-
> - **`um`**: The on-chain object ID of `three_body_contact` (created in Step 8). The SDK validator `checkCustomerRequiredNeedsUm()` requires this whenever `customer_required` is non-empty — without a Contact the Service has no encrypted channel to receive customer private info.
|
|
1166
|
-
> - **`order_allocators.allocators[].guard`**: Returns the on-chain object ID of `three_body_allocator_guard` (created in Step 9). This Guard verifies `order.service == three_body_signature_service` (R-C3-05 protection).
|
|
1167
|
-
> - **`order_allocators.allocators[].sharing[].who`**: `{"Entity": "0x..."}` indicates funds flow to the fixed Treasury object (`three_body_treasury` from Step 7). 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.
|
|
1168
|
-
> - **`order_allocators.allocators[].sharing[].mode`**: `1` is the numeric enum for `Rate` mode (input accepts the string `"Rate"`, output returns the numeric `1`).
|
|
1169
|
-
> - **`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.
|
|
1170
|
-
> - **`sales[].price`**: Returns the on-chain smallest-unit value as a string (`"888000000000"` = 888 WOW; WOW has 9 decimals). The input accepts the display format `"888WOW"` (auto-converted by the Fund Processing Layer) or the raw smallest-unit integer `888000000000`.
|
|
1171
|
-
|
|
1172
|
-
---
|
|
1173
|
-
|
|
1174
|
-
## Testing the Buy Guard
|
|
1175
|
-
|
|
1176
|
-
### Test 1: Author Purchase (Should Succeed)
|
|
1177
|
-
|
|
1178
|
-
The author (`three_body_author`) should be able to purchase the service.
|
|
1179
|
-
|
|
1180
|
-
**Request**:
|
|
1181
|
-
```json
|
|
1182
|
-
{
|
|
1183
|
-
"tool": "onchain_operations",
|
|
1184
|
-
"data": {
|
|
1185
|
-
"operation_type": "service",
|
|
1186
|
-
"data": {
|
|
1187
|
-
"object": "three_body_signature_service",
|
|
1188
|
-
"order_new": {
|
|
1189
|
-
"buy": {
|
|
1190
|
-
"items": [
|
|
1191
|
-
{
|
|
1192
|
-
"name": "Three-Body Book Signature",
|
|
1193
|
-
"stock": 1,
|
|
1194
|
-
"wip_hash": ""
|
|
1195
|
-
}
|
|
1196
|
-
],
|
|
1197
|
-
"total_pay": {
|
|
1198
|
-
"balance": "888WOW"
|
|
1199
|
-
}
|
|
1200
|
-
},
|
|
1201
|
-
"namedNewOrder": {
|
|
1202
|
-
"name": "three_body_order",
|
|
1203
|
-
"replaceExistName": true
|
|
1204
|
-
},
|
|
1205
|
-
"namedNewProgress": {
|
|
1206
|
-
"name": "three_body_progress",
|
|
1207
|
-
"replaceExistName": true
|
|
1208
|
-
},
|
|
1209
|
-
"namedNewAllocation": {
|
|
1210
|
-
"name": "three_body_allocation",
|
|
1211
|
-
"replaceExistName": true
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
},
|
|
1215
|
-
"env": {
|
|
1216
|
-
"account": "three_body_author",
|
|
1217
|
-
"network": "testnet"
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
```
|
|
1222
|
-
|
|
1223
|
-
> **Amount Format**: `"888WOW"` is the display format — the Fund Processing Layer converts it to `888000000000` smallest units (WOW has 9 decimals) before submission. The raw integer `888000000000` is equally valid. The order pays exactly the sale price set in Step 11.
|
|
1224
|
-
|
|
1225
|
-
**Expected Result**:
|
|
1226
|
-
```json
|
|
1227
|
-
{
|
|
1228
|
-
"result": {
|
|
1229
|
-
"status": "success",
|
|
1230
|
-
"data": {
|
|
1231
|
-
"result": {
|
|
1232
|
-
"type": "transaction",
|
|
1233
|
-
"objectChanges": [
|
|
1234
|
-
{
|
|
1235
|
-
"type": "Service",
|
|
1236
|
-
"type_raw": "0x2::service::Service<0x2::wow::WOW>",
|
|
1237
|
-
"object": "0x...",
|
|
1238
|
-
"version": "...",
|
|
1239
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1240
|
-
"change": "mutated"
|
|
1241
|
-
},
|
|
1242
|
-
{
|
|
1243
|
-
"type": "TableItem_EntityLinker",
|
|
1244
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
1245
|
-
"object": "0x...",
|
|
1246
|
-
"version": "...",
|
|
1247
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
1248
|
-
"change": "mutated"
|
|
1249
|
-
},
|
|
1250
|
-
{
|
|
1251
|
-
"type": "TableItem_EntityLinker",
|
|
1252
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
1253
|
-
"object": "0x...",
|
|
1254
|
-
"version": "...",
|
|
1255
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
1256
|
-
"change": "mutated"
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
"type": "TableItem_EntityLinker",
|
|
1260
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::registrar::Votes>",
|
|
1261
|
-
"object": "0x...",
|
|
1262
|
-
"version": "...",
|
|
1263
|
-
"owner": {"ObjectOwner": "0x0000000000000000000000000000000000000000000000000000000000000aaa"},
|
|
1264
|
-
"change": "created"
|
|
1265
|
-
},
|
|
1266
|
-
{
|
|
1267
|
-
"type": "Allocation",
|
|
1268
|
-
"type_raw": "0x2::allocation::Allocation<0x2::wow::WOW>",
|
|
1269
|
-
"object": "0x...",
|
|
1270
|
-
"version": "...",
|
|
1271
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1272
|
-
"change": "created"
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
"type": "Order",
|
|
1276
|
-
"type_raw": "0x2::order::Order",
|
|
1277
|
-
"object": "0x...",
|
|
1278
|
-
"version": "...",
|
|
1279
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1280
|
-
"change": "created"
|
|
1281
|
-
},
|
|
1282
|
-
{
|
|
1283
|
-
"type": "Progress",
|
|
1284
|
-
"type_raw": "0x2::progress::Progress",
|
|
1285
|
-
"object": "0x...",
|
|
1286
|
-
"version": "...",
|
|
1287
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1288
|
-
"change": "created"
|
|
1289
|
-
}
|
|
1290
|
-
]
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
},
|
|
1294
|
-
"schema": null
|
|
1295
|
-
}
|
|
1296
|
-
```
|
|
1297
|
-
|
|
1298
|
-
> **Why So Many Objects? (Information Injection Design)**
|
|
1299
|
-
>
|
|
1300
|
-
> 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:
|
|
1301
|
-
>
|
|
1302
|
-
> 1. **`Service` (mutated)**: The service's inventory/stock is decremented as a side effect of the purchase.
|
|
1303
|
-
> 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.
|
|
1304
|
-
> 3. **`Allocation` (created)**: The fund allocation object that will distribute the payment according to `order_allocators`.
|
|
1305
|
-
> 4. **`Order` (created)**: The primary order object tracking the purchase.
|
|
1306
|
-
> 5. **`Progress` (created)**: The workflow progress object tracking the order through the Machine nodes.
|
|
1307
|
-
>
|
|
1308
|
-
> 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.
|
|
1309
|
-
>
|
|
1310
|
-
> 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.
|
|
1311
|
-
|
|
1312
|
-
---
|
|
1313
|
-
|
|
1314
|
-
### Test 2: Non-Author Purchase (Should Fail)
|
|
1315
|
-
|
|
1316
|
-
Any other account attempting to purchase should fail with Buy Guard verification error.
|
|
1317
|
-
|
|
1318
|
-
**Request**:
|
|
1319
|
-
```json
|
|
1320
|
-
{
|
|
1321
|
-
"tool": "onchain_operations",
|
|
1322
|
-
"data": {
|
|
1323
|
-
"operation_type": "service",
|
|
1324
|
-
"data": {
|
|
1325
|
-
"object": "three_body_signature_service",
|
|
1326
|
-
"order_new": {
|
|
1327
|
-
"buy": {
|
|
1328
|
-
"items": [
|
|
1329
|
-
{
|
|
1330
|
-
"name": "Three-Body Book Signature",
|
|
1331
|
-
"stock": 1,
|
|
1332
|
-
"wip_hash": ""
|
|
1333
|
-
}
|
|
1334
|
-
],
|
|
1335
|
-
"total_pay": {
|
|
1336
|
-
"balance": "888WOW"
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
},
|
|
1341
|
-
"env": {
|
|
1342
|
-
"account": "three_body_customer",
|
|
1343
|
-
"network": "testnet"
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
**Expected Result** (Error):
|
|
1350
|
-
```
|
|
1351
|
-
Error: Error Description: Verification failed
|
|
1352
|
-
Transaction resolution failed: MoveAbort in 8th command, abort code: 7 (Verify failed), in '0x0000000000000000000000000000000000000000000000000000000000000002::passport::result_for_guard' (instruction 17)
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
> **Error Format Explained (Information Injection Design)**
|
|
1356
|
-
>
|
|
1357
|
-
> 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:
|
|
1358
|
-
>
|
|
1359
|
-
> - **`Error: ` prefix**: Added by the MCP handler (`handler.ts`) to mark the response as an error.
|
|
1360
|
-
> - **`Error Description: Verification failed`**: A human-readable classification of the error category, generated by the `classifyError` logic.
|
|
1361
|
-
> - **`Transaction resolution failed: MoveAbort in 8th command`**: The raw Sui SDK error indicating which transaction command (8th) aborted.
|
|
1362
|
-
> - **`abort code: 7 (Verify failed)`**: The Move abort code (7) translated to its semantic meaning (`Verify failed`).
|
|
1363
|
-
> - **`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).
|
|
1364
|
-
> - **`(instruction 17)`**: The specific instruction index within the Move function where the abort occurred.
|
|
1365
|
-
>
|
|
1366
|
-
> 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.
|
|
1367
|
-
|
|
1368
|
-
---
|
|
1369
|
-
|
|
1370
|
-
## Workflow Execution
|
|
1371
|
-
|
|
1372
|
-
After a successful purchase by the author, the order progresses through the Machine nodes, and the payment is then released to the Treasury via the order allocator:
|
|
1373
|
-
|
|
1374
|
-
### Node 1: Book Delivered
|
|
1375
|
-
|
|
1376
|
-
The author confirms the book has been delivered.
|
|
1377
|
-
|
|
1378
|
-
**Request**:
|
|
1379
|
-
```json
|
|
1380
|
-
{
|
|
1381
|
-
"tool": "onchain_operations",
|
|
1382
|
-
"data": {
|
|
1383
|
-
"operation_type": "progress",
|
|
1384
|
-
"data": {
|
|
1385
|
-
"object": "three_body_progress",
|
|
1386
|
-
"operate": {
|
|
1387
|
-
"operation": {
|
|
1388
|
-
"next_node_name": "Book Delivered",
|
|
1389
|
-
"forward": "Confirm Delivery"
|
|
1390
|
-
},
|
|
1391
|
-
"op": "next"
|
|
1392
|
-
}
|
|
1393
|
-
},
|
|
1394
|
-
"env": {
|
|
1395
|
-
"account": "three_body_author",
|
|
1396
|
-
"network": "testnet",
|
|
1397
|
-
"no_cache": true
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
```
|
|
1402
|
-
|
|
1403
|
-
> **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.
|
|
1404
|
-
|
|
1405
|
-
**Expected Result**:
|
|
1406
|
-
```json
|
|
1407
|
-
{
|
|
1408
|
-
"result": {
|
|
1409
|
-
"status": "success",
|
|
1410
|
-
"data": {
|
|
1411
|
-
"result": {
|
|
1412
|
-
"type": "transaction",
|
|
1413
|
-
"objectChanges": [
|
|
1414
|
-
{
|
|
1415
|
-
"type": "Progress",
|
|
1416
|
-
"type_raw": "0x2::progress::Progress",
|
|
1417
|
-
"object": "0x...",
|
|
1418
|
-
"version": "...",
|
|
1419
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1420
|
-
"change": "mutated"
|
|
1421
|
-
},
|
|
1422
|
-
{
|
|
1423
|
-
"type": "TableItem_ProgressHistory",
|
|
1424
|
-
"type_raw": "0x2::dynamic_field::Field<u64, 0x2::progress::History>",
|
|
1425
|
-
"object": "0x...",
|
|
1426
|
-
"version": "...",
|
|
1427
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
1428
|
-
"change": "created"
|
|
1429
|
-
}
|
|
1430
|
-
]
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
},
|
|
1434
|
-
"schema": null
|
|
1435
|
-
}
|
|
1436
|
-
```
|
|
1437
|
-
|
|
1438
|
-
### Node 2: Signature Completed
|
|
1439
|
-
|
|
1440
|
-
The author completes the signature.
|
|
1441
|
-
|
|
1442
|
-
> **Important**: Use `no_cache: true` in the `env` for sequential Progress operations on the same object. This ensures the SDK reads the latest on-chain state (including the updated `current` node) instead of using a cached version.
|
|
1443
|
-
|
|
1444
|
-
**Request**:
|
|
1445
|
-
```json
|
|
1446
|
-
{
|
|
1447
|
-
"tool": "onchain_operations",
|
|
1448
|
-
"data": {
|
|
1449
|
-
"operation_type": "progress",
|
|
1450
|
-
"data": {
|
|
1451
|
-
"object": "three_body_progress",
|
|
1452
|
-
"operate": {
|
|
1453
|
-
"operation": {
|
|
1454
|
-
"next_node_name": "Signature Completed",
|
|
1455
|
-
"forward": "Complete Signature"
|
|
1456
|
-
},
|
|
1457
|
-
"op": "next"
|
|
1458
|
-
}
|
|
1459
|
-
},
|
|
1460
|
-
"env": {
|
|
1461
|
-
"account": "three_body_author",
|
|
1462
|
-
"network": "testnet",
|
|
1463
|
-
"no_cache": true
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
```
|
|
1468
|
-
|
|
1469
|
-
**Expected Result**:
|
|
1470
|
-
```json
|
|
1471
|
-
{
|
|
1472
|
-
"result": {
|
|
1473
|
-
"status": "success",
|
|
1474
|
-
"data": {
|
|
1475
|
-
"result": {
|
|
1476
|
-
"type": "transaction",
|
|
1477
|
-
"objectChanges": [
|
|
1478
|
-
{
|
|
1479
|
-
"type": "Progress",
|
|
1480
|
-
"type_raw": "0x2::progress::Progress",
|
|
1481
|
-
"object": "0x...",
|
|
1482
|
-
"version": "...",
|
|
1483
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1484
|
-
"change": "mutated"
|
|
1485
|
-
},
|
|
1486
|
-
{
|
|
1487
|
-
"type": "TableItem_ProgressHistory",
|
|
1488
|
-
"type_raw": "0x2::dynamic_field::Field<u64, 0x2::progress::History>",
|
|
1489
|
-
"object": "0x...",
|
|
1490
|
-
"version": "...",
|
|
1491
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
1492
|
-
"change": "created"
|
|
1493
|
-
}
|
|
1494
|
-
]
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
},
|
|
1498
|
-
"schema": null
|
|
1499
|
-
}
|
|
1500
|
-
```
|
|
1501
|
-
|
|
1502
|
-
### Fund Allocation: Release the 888 WOW Payment to the Treasury
|
|
1503
|
-
|
|
1504
|
-
Once the Progress reaches the final node (`Signature Completed`), the order is fulfilled and the 888 WOW payment held by `three_body_allocation` can be distributed. The Service's `order_allocators` (Step 10) routes 100% to `three_body_treasury` when `three_body_allocator_guard` (Step 9) verifies `order.service == three_body_signature_service`.
|
|
1505
|
-
|
|
1506
|
-
#### (a) Trigger the Allocation (`alloc_by_guard`)
|
|
1507
|
-
|
|
1508
|
-
The allocator Guard's table item `0` has `b_submission: true` (the Order address is only known at runtime), so the call must carry a **top-level `submission` block** — at the SAME level as `data` and `env`, NOT inside `data.data`.
|
|
1509
|
-
|
|
1510
|
-
**Request**:
|
|
1511
|
-
```json
|
|
1512
|
-
{
|
|
1513
|
-
"tool": "onchain_operations",
|
|
1514
|
-
"data": {
|
|
1515
|
-
"operation_type": "allocation",
|
|
1516
|
-
"data": {
|
|
1517
|
-
"object": "three_body_allocation",
|
|
1518
|
-
"alloc_by_guard": "three_body_allocator_guard"
|
|
1519
|
-
},
|
|
1520
|
-
"env": {
|
|
1521
|
-
"account": "three_body_author",
|
|
1522
|
-
"network": "testnet"
|
|
1523
|
-
},
|
|
1524
|
-
"submission": {
|
|
1525
|
-
"type": "submission",
|
|
1526
|
-
"guard": [
|
|
1527
|
-
{
|
|
1528
|
-
"object": "three_body_allocator_guard",
|
|
1529
|
-
"impack": true
|
|
1530
|
-
}
|
|
1531
|
-
],
|
|
1532
|
-
"submission": [
|
|
1533
|
-
{
|
|
1534
|
-
"guard": "three_body_allocator_guard",
|
|
1535
|
-
"submission": [
|
|
1536
|
-
{
|
|
1537
|
-
"identifier": 0,
|
|
1538
|
-
"b_submission": true,
|
|
1539
|
-
"value_type": "Address",
|
|
1540
|
-
"value": "three_body_order",
|
|
1541
|
-
"name": "Order ID (submitted at runtime)"
|
|
1542
|
-
}
|
|
1543
|
-
]
|
|
1544
|
-
}
|
|
1545
|
-
]
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
```
|
|
1550
|
-
|
|
1551
|
-
> **Two-Phase Reminder**: The ConfirmGate does not gate `allocation` operations, but treat fund distribution as irreversible in production — preview first (call without `confirmed`), then execute after user approval.
|
|
1552
|
-
|
|
1553
|
-
**Expected Result**:
|
|
1554
|
-
```json
|
|
1555
|
-
{
|
|
1556
|
-
"result": {
|
|
1557
|
-
"status": "success",
|
|
1558
|
-
"data": {
|
|
1559
|
-
"result": {
|
|
1560
|
-
"type": "transaction",
|
|
1561
|
-
"objectChanges": [
|
|
1562
|
-
{
|
|
1563
|
-
"type": "Allocation",
|
|
1564
|
-
"type_raw": "0x2::allocation::Allocation<0x2::wow::WOW>",
|
|
1565
|
-
"object": "0x...",
|
|
1566
|
-
"version": "...",
|
|
1567
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1568
|
-
"change": "mutated"
|
|
1569
|
-
},
|
|
1570
|
-
{
|
|
1571
|
-
"type": "Payment",
|
|
1572
|
-
"type_raw": "0x2::payment::Payment<0x2::wow::WOW>",
|
|
1573
|
-
"object": "0x...",
|
|
1574
|
-
"version": "...",
|
|
1575
|
-
"owner": "Immutable",
|
|
1576
|
-
"change": "created"
|
|
1577
|
-
}
|
|
1578
|
-
]
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
},
|
|
1582
|
-
"schema": null
|
|
1583
|
-
}
|
|
1584
|
-
```
|
|
1585
|
-
|
|
1586
|
-
> The immutable `Payment` object created by this call is the on-chain receipt of the distribution — its `payment` array records each recipient and amount (here: 100% of 888 WOW to `three_body_treasury`).
|
|
1587
|
-
|
|
1588
|
-
#### (b) Verify the Pending CoinWrapper at the Treasury
|
|
1589
|
-
|
|
1590
|
-
The Treasury does NOT receive spendable coins directly — each allocation recipient receives a `CoinWrapper` object that must be unwrapped before the funds enter its balance. Query the Treasury's received objects:
|
|
1591
|
-
|
|
1592
|
-
**Request**:
|
|
1593
|
-
```json
|
|
1594
|
-
{
|
|
1595
|
-
"tool": "query_toolkit",
|
|
1596
|
-
"data": {
|
|
1597
|
-
"query_type": "onchain_received",
|
|
1598
|
-
"name_or_address": "three_body_treasury",
|
|
1599
|
-
"network": "testnet"
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
```
|
|
1603
|
-
|
|
1604
|
-
**Expected Result**:
|
|
1605
|
-
```json
|
|
1606
|
-
{
|
|
1607
|
-
"result": {
|
|
1608
|
-
"status": "success",
|
|
1609
|
-
"data": {
|
|
1610
|
-
"result": {
|
|
1611
|
-
"query_type": "onchain_received",
|
|
1612
|
-
"result": {
|
|
1613
|
-
"balance": "888000000000",
|
|
1614
|
-
"token_type": "0x2::wow::WOW",
|
|
1615
|
-
"received": [
|
|
1616
|
-
{
|
|
1617
|
-
"id": "0x...",
|
|
1618
|
-
"balance": "888000000000",
|
|
1619
|
-
"payment": "0x..."
|
|
1620
|
-
}
|
|
1621
|
-
]
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
},
|
|
1626
|
-
"schema": null
|
|
1627
|
-
}
|
|
1628
|
-
```
|
|
1629
|
-
|
|
1630
|
-
> The CoinWrapper holds the full 888 WOW (`888000000000` smallest units — Rate `10000` = 100% of the order payment) and references the `Payment` receipt created in step (a).
|
|
1631
|
-
|
|
1632
|
-
#### (c) Unwrap into the Treasury Balance (`receive`)
|
|
1633
|
-
|
|
1634
|
-
Deposit the pending CoinWrapper into the Treasury's balance. The literal `"recently"` auto-receives ALL recently received CoinWrappers:
|
|
1635
|
-
|
|
1636
|
-
**Request**:
|
|
1637
|
-
```json
|
|
1638
|
-
{
|
|
1639
|
-
"tool": "onchain_operations",
|
|
1640
|
-
"data": {
|
|
1641
|
-
"operation_type": "treasury",
|
|
1642
|
-
"data": {
|
|
1643
|
-
"object": "three_body_treasury",
|
|
1644
|
-
"receive": "recently"
|
|
1645
|
-
},
|
|
1646
|
-
"env": {
|
|
1647
|
-
"account": "three_body_author",
|
|
1648
|
-
"network": "testnet"
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
```
|
|
1653
|
-
|
|
1654
|
-
**Expected Result**:
|
|
1655
|
-
```json
|
|
1656
|
-
{
|
|
1657
|
-
"result": {
|
|
1658
|
-
"status": "success",
|
|
1659
|
-
"data": {
|
|
1660
|
-
"result": {
|
|
1661
|
-
"type": "transaction",
|
|
1662
|
-
"objectChanges": [
|
|
1663
|
-
{
|
|
1664
|
-
"type": "Treasury",
|
|
1665
|
-
"type_raw": "0x2::treasury::Treasury<0x2::wow::WOW>",
|
|
1666
|
-
"object": "0x...",
|
|
1667
|
-
"version": "...",
|
|
1668
|
-
"owner": {"Shared": {"initial_shared_version": "..."}},
|
|
1669
|
-
"change": "mutated"
|
|
1670
|
-
},
|
|
1671
|
-
{
|
|
1672
|
-
"type": "TableItem_TreasuryHistory",
|
|
1673
|
-
"type_raw": "0x2::dynamic_field::Field<address, 0x2::parent_linked_table::Node<address, 0x2::treasury::Record>>",
|
|
1674
|
-
"object": "0x...",
|
|
1675
|
-
"version": "...",
|
|
1676
|
-
"owner": {"ObjectOwner": "0x..."},
|
|
1677
|
-
"change": "created"
|
|
1678
|
-
}
|
|
1679
|
-
]
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
},
|
|
1683
|
-
"schema": null
|
|
1684
|
-
}
|
|
1685
|
-
```
|
|
1686
|
-
|
|
1687
|
-
> **Permission**: The author passes as the Permission's admin (see Step 1). A non-admin operator would additionally need the built-in `TREASURY_RECEIVE` index (`253`).
|
|
1688
|
-
|
|
1689
|
-
#### (d) Final Verification
|
|
1690
|
-
|
|
1691
|
-
Query the Treasury to confirm the funds have landed in its balance:
|
|
1692
|
-
|
|
1693
|
-
**Request**:
|
|
1694
|
-
```json
|
|
1695
|
-
{
|
|
1696
|
-
"tool": "query_toolkit",
|
|
1697
|
-
"data": {
|
|
1698
|
-
"query_type": "onchain_objects",
|
|
1699
|
-
"objects": ["three_body_treasury"],
|
|
1700
|
-
"no_cache": true,
|
|
1701
|
-
"network": "testnet"
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
```
|
|
1705
|
-
|
|
1706
|
-
**Expected Result** (key fields):
|
|
1707
|
-
```json
|
|
1708
|
-
{
|
|
1709
|
-
"result": {
|
|
1710
|
-
"status": "success",
|
|
1711
|
-
"data": {
|
|
1712
|
-
"result": {
|
|
1713
|
-
"query_type": "onchain_objects",
|
|
1714
|
-
"result": {
|
|
1715
|
-
"objects": [
|
|
1716
|
-
{
|
|
1717
|
-
"object": "0x...",
|
|
1718
|
-
"type": "Treasury",
|
|
1719
|
-
"type_raw": "0x2::treasury::Treasury<0x2::wow::WOW>",
|
|
1720
|
-
"balance": "888000000000",
|
|
1721
|
-
"inflow": "888000000000",
|
|
1722
|
-
"outflow": "0",
|
|
1723
|
-
"history_count": 1,
|
|
1724
|
-
"query_name": "three_body_treasury"
|
|
1725
|
-
}
|
|
1726
|
-
]
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
},
|
|
1731
|
-
"schema": null
|
|
1732
|
-
}
|
|
1733
|
-
```
|
|
1734
|
-
|
|
1735
|
-
> The full 888 WOW order payment now sits in `three_body_treasury` (`balance` = `888000000000` = 888 WOW) — the Treasury-first fund flow (Step 7) executed end-to-end: Order → Allocation → Guard-verified distribution → Treasury.
|
|
1736
|
-
|
|
1737
|
-
---
|
|
1738
|
-
|
|
1739
|
-
## Summary
|
|
1740
|
-
|
|
1741
|
-
This example demonstrates:
|
|
1742
|
-
|
|
1743
|
-
1. **Buy Guard Implementation**: Restricts service purchases to specific accounts (Level 1 strict single-identity binding)
|
|
1744
|
-
2. **Machine Workflow**: Two-node process for service delivery tracking
|
|
1745
|
-
3. **WIP Files Optional**: Sales items can use WIP files or empty strings
|
|
1746
|
-
4. **Service Configuration**: Complete setup from creation to publication
|
|
1747
|
-
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)
|
|
1748
|
-
6. **Fund Allocation Execution**: `alloc_by_guard` distributes the completed order's 888 WOW payment to `three_body_treasury`, and the pending CoinWrapper is unwrapped via Treasury `receive` (see Workflow Execution → Fund Allocation)
|
|
1749
|
-
|
|
1750
|
-
### Key Objects
|
|
1751
|
-
|
|
1752
|
-
| Object | Name |
|
|
1753
|
-
|--------|------|
|
|
1754
|
-
| Permission | three_body_permission |
|
|
1755
|
-
| Buy Guard | three_body_buy_guard (Level 1 strict, R-C4-04) |
|
|
1756
|
-
| Machine | three_body_machine |
|
|
1757
|
-
| Service | three_body_signature_service |
|
|
1758
|
-
| Treasury | three_body_treasury |
|
|
1759
|
-
| Contact (um) | three_body_contact (required for customer_required — SDK-enforced customer_required ⟶ um linkage) |
|
|
1760
|
-
| Allocator Guard | three_body_allocator_guard (Level 3 scene-combined, R-C3-05/R-C3-06 safe) |
|
|
1761
|
-
| Order | three_body_order |
|
|
1762
|
-
| Allocation | three_body_allocation |
|
|
1763
|
-
| Progress | three_body_progress |
|
|
1764
|
-
|
|
1765
|
-
### Risk Mitigation Summary
|
|
1766
|
-
|
|
1767
|
-
| Risk | Severity | Mitigation |
|
|
1768
|
-
|------|----------|------------|
|
|
1769
|
-
| **R-C3-05** (Cross-service theft) | High | `three_body_allocator_guard` verifies `order.service == three_body_signature_service` before allocation |
|
|
1770
|
-
| **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 |
|
|
1771
|
-
| **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) |
|
|
1772
|
-
|
|
1773
|
-
---
|
|
1774
|
-
|
|
1775
|
-
## Design Notes
|
|
1776
|
-
|
|
1777
|
-
### Why Buy Guard?
|
|
1778
|
-
|
|
1779
|
-
The Buy Guard ensures only the intended user (the author) can purchase this service. This is useful when:
|
|
1780
|
-
- The service is an internal tool
|
|
1781
|
-
- Purchase authorization requires verification
|
|
1782
|
-
- The service is part of a larger workflow controlled by a specific entity
|
|
1783
|
-
|
|
1784
|
-
### WIP Files Optional
|
|
1785
|
-
|
|
1786
|
-
This example shows both options for sales items:
|
|
1787
|
-
- Can use WIP files with hash validation
|
|
1788
|
-
- Can use empty wip/wip_hash for simpler setup
|
|
1789
|
-
- WIP validation is skipped when wip is empty string
|
|
1790
|
-
|
|
1791
|
-
### Workflow Design
|
|
1792
|
-
|
|
1793
|
-
The two-node workflow provides clear tracking:
|
|
1794
|
-
1. **Book Delivered**: Confirms physical delivery of the book
|
|
1795
|
-
2. **Signature Completed**: Confirms the author has signed the book
|
|
1796
|
-
|
|
1797
|
-
Each node transition requires the author's confirmation, ensuring accountability.
|
|
1798
|
-
|
|
1799
|
-
### Best Practices
|
|
1800
|
-
|
|
1801
|
-
1. **Naming Strategy**: Use consistent naming conventions and `replaceExistName: true` to enforce name usage. All operations use names instead of addresses for readability.
|
|
1802
|
-
|
|
1803
|
-
2. **Execution Order Matters**: Publish operations lock objects. Follow this order:
|
|
1804
|
-
- Permission first (foundation)
|
|
1805
|
-
- Machine (create workflow before service)
|
|
1806
|
-
- Service (unpublished)
|
|
1807
|
-
- Guards (Buy Guard for purchase control; Allocator Guard needs Service address for `order.service` verification)
|
|
1808
|
-
- Treasury (uses same Permission as Service for unified governance)
|
|
1809
|
-
- Contact (um) — REQUIRED before setting customer_required (SDK-enforced: customer_required ⟶ um hard linkage; validator also re-runs at publish time)
|
|
1810
|
-
- Configure Service (add machine, buy_guard, order_allocators with allocator guard + Entity(Treasury); set customer_required **together with** um in the same or prior call)
|
|
1811
|
-
- Publish Service (LAST - once published, many changes are blocked)
|
|
1812
|
-
|
|
1813
|
-
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.
|
|
1814
|
-
|
|
1815
|
-
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:
|
|
1816
|
-
- Any operation whose **top-level** `data.namedNew ?? data.object` sets `replaceExistName: true` (unbinds existing names). Note: ConfirmGate's default-value warnings only scan that top-level field — the NESTED naming fields inside `order_new` (`namedNewOrder`/`namedNewProgress`/`namedNewAllocation`) are NOT scanned, which is why Test 1 runs without `confirmed: true` despite its nested `replaceExistName: true` entries.
|
|
1817
|
-
- Any `publish: true` operation (irreversible lock on Machine `nodes`/`pairs`/`forwards`, or Service `machine`/`order_allocators`)
|
|
1818
|
-
Without `confirmed: true`, the server returns a `pending_confirmation` result and blocks the transaction until you re-call with `confirmed: true`.
|
|
1819
|
-
|
|
1820
|
-
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.
|
|
1821
|
-
|
|
1822
|
-
6. **customer_required ⟶ um (Contact) Hard Linkage (SDK-enforced)**: Whenever you set `customer_required` (e.g. `["phone", "email", "shipping_address"]`), you MUST also bind a Contact object via `um` in the SAME or a prior Service call. The SDK's `checkCustomerRequiredNeedsUm()` validator in `ts-sdk/packages/wowok/src/w/call/service.ts` (L1226) runs at TWO points:
|
|
1823
|
-
- When `customer_required` is present in the current operation `data` (immediate block, L307)
|
|
1824
|
-
- When `publish: true` is set (re-verifies against the accumulated Service state, L314)
|
|
1825
|
-
This means splitting the two calls (set `customer_required` first, add `um` later) is NOT safe — the publish-time re-check would still block. Always create the Contact first, then set both `customer_required` and `um` together.
|
|
1826
|
-
|
|
1827
|
-
7. **Query Toolkit is Your Best Friend**: Use queries constantly to verify objects exist, check configurations, debug issues, and confirm state changes.
|
|
1828
|
-
|
|
1829
|
-
8. **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`, `um`. 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"`.
|
|
1830
|
-
|
|
1831
|
-
9. **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.
|