@wowok/skills 3.0.4 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +5 -4
- package/scripts/install.js +21 -859
- 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 +10 -30
- package/wowok-machine/SKILL.md +5 -18
- package/wowok-market/SKILL.md +5 -21
- package/wowok-messenger/SKILL.md +32 -50
- package/wowok-onboard/SKILL.md +5 -22
- package/wowok-order/SKILL.md +6 -19
- package/wowok-output/SKILL.md +5 -10
- package/wowok-planner/SKILL.md +6 -20
- package/wowok-provider/SKILL.md +6 -18
- 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 -2880
- 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,1849 +0,0 @@
|
|
|
1
|
-
# Iceland Travel Service Example
|
|
2
|
-
|
|
3
|
-
A complete example demonstrating how to create an Iceland travel service using WoWok protocol. This service integrates weather-dependent activities and multi-node workflow management. (Note: "Buy Insurance" is just the name of the first workflow node in this example — there is no real insurance sub-order mechanism.)
|
|
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 in sequence before testing any customer or order flow. Do not skip steps — each depends on objects created by prior steps.
|
|
12
|
-
- **Prerequisites**: `travel_provider`, `weather_provider`, and `alice` (test customer) with sufficient WOW for gas. 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.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
> **💡 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.
|
|
26
|
-
|
|
27
|
-
## Core Requirements & Features
|
|
28
|
-
|
|
29
|
-
| Requirement | Description | Implementation |
|
|
30
|
-
|-------------|-------------|----------------|
|
|
31
|
-
| **Weather Data Query** | Demonstrate querying weather Repository | Guard checks if weather data exists for a given date |
|
|
32
|
-
| **Multi-Node Workflow** | Start -> Buy Insurance -> SPA -> Ice Scooting -> Complete | Machine with 5 nodes and conditional paths |
|
|
33
|
-
| **Time-Lock Completion** | Prevent premature order completion | Guard using Order + convert_witness(TypeOrderProgress) |
|
|
34
|
-
| **Cancellation Support** | Allow order cancellation from Ice Scooting node | Cancel forward with permission-based access |
|
|
35
|
-
| **Arbitration** | Dispute resolution for order conflicts | Arbitration object bound to Service |
|
|
36
|
-
| **Revenue Allocation** | Distribute funds based on progress state | Three allocation Guards for different refund scenarios |
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Prerequisites
|
|
41
|
-
|
|
42
|
-
Before running this example, ensure you have:
|
|
43
|
-
|
|
44
|
-
1. An account named `travel_provider` with sufficient WOW tokens
|
|
45
|
-
2. An account named `weather_provider` with sufficient WOW tokens
|
|
46
|
-
3. An account named `alice` (test customer) with sufficient WOW tokens
|
|
47
|
-
4. Access to the WoWok MCP server
|
|
48
|
-
|
|
49
|
-
### Create Accounts
|
|
50
|
-
|
|
51
|
-
**Prompt**: Create accounts for travel provider, weather provider, and test customer.
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"tool": "account_operation",
|
|
56
|
-
"data": {
|
|
57
|
-
"gen": {
|
|
58
|
-
"name": "travel_provider",
|
|
59
|
-
"replaceExistName": true
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
```json
|
|
66
|
-
{
|
|
67
|
-
"tool": "account_operation",
|
|
68
|
-
"data": {
|
|
69
|
-
"gen": {
|
|
70
|
-
"name": "weather_provider",
|
|
71
|
-
"replaceExistName": true
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"tool": "account_operation",
|
|
80
|
-
"data": {
|
|
81
|
-
"gen": {
|
|
82
|
-
"name": "alice",
|
|
83
|
-
"replaceExistName": true
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Get Test Tokens
|
|
90
|
-
|
|
91
|
-
**Prompt**: Request testnet WOW tokens for all accounts.
|
|
92
|
-
|
|
93
|
-
```json
|
|
94
|
-
{
|
|
95
|
-
"tool": "account_operation",
|
|
96
|
-
"data": {
|
|
97
|
-
"faucet": {
|
|
98
|
-
"network": "testnet",
|
|
99
|
-
"name_or_address": "travel_provider"
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"tool": "account_operation",
|
|
108
|
-
"data": {
|
|
109
|
-
"faucet": {
|
|
110
|
-
"network": "testnet",
|
|
111
|
-
"name_or_address": "weather_provider"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
```json
|
|
118
|
-
{
|
|
119
|
-
"tool": "account_operation",
|
|
120
|
-
"data": {
|
|
121
|
-
"faucet": {
|
|
122
|
-
"network": "testnet",
|
|
123
|
-
"name_or_address": "alice"
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## Environment Parameters
|
|
132
|
-
|
|
133
|
-
All on-chain operations in this example use the following `env` fields:
|
|
134
|
-
|
|
135
|
-
| Field | Value | Purpose |
|
|
136
|
-
|-------|-------|---------|
|
|
137
|
-
| `network` | `"testnet"` | Target network |
|
|
138
|
-
| `account` | Account name | Signer account for the transaction |
|
|
139
|
-
| `no_cache` | `true` | Bypass local cache to avoid stale reads during sequential object creation. Without this, operations that depend on recently created objects may fail with "object not found". |
|
|
140
|
-
| `confirmed` | `true` | Explicitly confirm the on-chain transaction (MCP ConfirmGate). Required for all write operations. |
|
|
141
|
-
|
|
142
|
-
Additionally, `onChain: true` is required when an object's name needs to be resolved across different accounts (see Step 0.3).
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Step 0: Setup Weather Data
|
|
147
|
-
|
|
148
|
-
Before creating the travel service, set up the weather Repository with the next 5 days of weather data. The weather data is keyed by timestamp, and the `weather_check_guard` (Step 3.1) will query this repository at runtime when the customer enters the "Ice Scooting" node (Step 7.3). You must use timestamps that are valid at the time you run this example.
|
|
149
|
-
|
|
150
|
-
### 0.1 Calculate Weather Timestamps
|
|
151
|
-
|
|
152
|
-
The weather record `id` is a UTC timestamp (in milliseconds). It must be **aligned to UTC 00:00:00** so that the same timestamp can be reproduced and submitted later in Step 7.3. Run the following JavaScript (also available as `calc-weather-timestamps.js` in this example folder):
|
|
153
|
-
|
|
154
|
-
```js
|
|
155
|
-
const DAY_MS = 86400000; // 24 * 60 * 60 * 1000
|
|
156
|
-
const now = Date.now();
|
|
157
|
-
// Align to UTC 00:00:00 of today, then compute the next 5 days.
|
|
158
|
-
// Repository data is keyed by timestamp, so the submitted activity date
|
|
159
|
-
// must match exactly the id used when adding weather data.
|
|
160
|
-
const todayStart = Math.floor(now / DAY_MS) * DAY_MS;
|
|
161
|
-
for (let i = 1; i <= 5; i++) {
|
|
162
|
-
const ts = todayStart + i * DAY_MS;
|
|
163
|
-
console.log(`Day ${i}: ${ts} (${new Date(ts).toISOString()})`);
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**Sample output** (run on 2026-07-08 — re-run the script to get current values for your test time):
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
Day 1: 1783555200000 (2026-07-09T00:00:00.000Z)
|
|
171
|
-
Day 2: 1783641600000 (2026-07-10T00:00:00.000Z)
|
|
172
|
-
Day 3: 1783728000000 (2026-07-11T00:00:00.000Z)
|
|
173
|
-
Day 4: 1783814400000 (2026-07-12T00:00:00.000Z)
|
|
174
|
-
Day 5: 1783900800000 (2026-07-13T00:00:00.000Z)
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
> **Important**: Always re-run the script at test time — the timestamps depend on the current date. Copy the 5 printed values; they will be used in Step 0.4 (as repository data `id`) and Step 7.3 (as the submitted `activity_date`). The two must match exactly, otherwise the Guard query `repository.data has("Condition", convert_number_address(activity_date))` will return false.
|
|
178
|
-
|
|
179
|
-
### 0.2 Create Weather Permission
|
|
180
|
-
|
|
181
|
-
**Prompt**: Create a Permission object named "weather_permission".
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{
|
|
185
|
-
"tool": "onchain_operations",
|
|
186
|
-
"data": {
|
|
187
|
-
"operation_type": "permission",
|
|
188
|
-
"data": {
|
|
189
|
-
"object": {
|
|
190
|
-
"name": "weather_permission",
|
|
191
|
-
"replaceExistName": true
|
|
192
|
-
},
|
|
193
|
-
"description": "Weather repository permission"
|
|
194
|
-
},
|
|
195
|
-
"env": {
|
|
196
|
-
"account": "weather_provider",
|
|
197
|
-
"network": "testnet",
|
|
198
|
-
"no_cache": true,
|
|
199
|
-
"confirmed": true
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 0.3 Create Weather Repository with Policies
|
|
206
|
-
|
|
207
|
-
**Prompt**: Create a Repository named "weather_repo" with "Condition" policy.
|
|
208
|
-
|
|
209
|
-
```json
|
|
210
|
-
{
|
|
211
|
-
"tool": "onchain_operations",
|
|
212
|
-
"data": {
|
|
213
|
-
"operation_type": "repository",
|
|
214
|
-
"data": {
|
|
215
|
-
"object": {
|
|
216
|
-
"name": "weather_repo",
|
|
217
|
-
"permission": "weather_permission",
|
|
218
|
-
"replaceExistName": true,
|
|
219
|
-
"onChain": true
|
|
220
|
-
},
|
|
221
|
-
"description": "Weather data repository for Iceland travel activities",
|
|
222
|
-
"policies": {
|
|
223
|
-
"op": "add",
|
|
224
|
-
"policy": [
|
|
225
|
-
{
|
|
226
|
-
"name": "Condition",
|
|
227
|
-
"description": "Weather condition policy for activity dates",
|
|
228
|
-
"write_guard": [{ "guard": "weather_write_guard" }],
|
|
229
|
-
"id_from": "None",
|
|
230
|
-
"value_type": "String"
|
|
231
|
-
}
|
|
232
|
-
]
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
"env": {
|
|
236
|
-
"account": "weather_provider",
|
|
237
|
-
"network": "testnet",
|
|
238
|
-
"no_cache": true,
|
|
239
|
-
"confirmed": true
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
> **Note**: `onChain: true` is required here because `weather_repo` is created by the `weather_provider` account, but its name will be referenced in the Guard table (Step 3.1) by the `travel_provider` account. Without `onChain: true`, the name is stored locally only on `weather_provider`'s device and cannot be resolved by `travel_provider`. When `onChain: true` is set, the name is published on-chain and becomes publicly visible, allowing cross-account name resolution.
|
|
246
|
-
>
|
|
247
|
-
> The `write_guard` in the "Condition" policy points to `weather_write_guard` (created above). Because `id_from: "None"` lets the writer choose data ids, the contract mandates a Guard to authorize writes. `data_add` in Step 0.4 needs no Guard submission — the Guard is always-true with no submission fields.
|
|
248
|
-
|
|
249
|
-
### 0.4 Add Weather Data
|
|
250
|
-
|
|
251
|
-
Add 5 days of weather data. The `weather_check_guard` (Step 3.1) only verifies that a record **exists** for the activity date (via `repository.data has`), so all 5 days will pass the Guard regardless of the condition value. The "rainy" value on Day 5 is informational only — to actually reject based on weather condition, you would need a Guard that queries `repository.data` and compares the value, which is more complex and not used in this example.
|
|
252
|
-
|
|
253
|
-
**Prompt**: Add weather data to "weather_repo" with policy "Condition".
|
|
254
|
-
|
|
255
|
-
```json
|
|
256
|
-
{
|
|
257
|
-
"tool": "onchain_operations",
|
|
258
|
-
"data": {
|
|
259
|
-
"operation_type": "repository",
|
|
260
|
-
"data": {
|
|
261
|
-
"object": "weather_repo",
|
|
262
|
-
"data_add": {
|
|
263
|
-
"name": "Condition",
|
|
264
|
-
"items": [
|
|
265
|
-
{
|
|
266
|
-
"data": [
|
|
267
|
-
{"id": <DAY1_TIMESTAMP>, "data": "sunny"},
|
|
268
|
-
{"id": <DAY2_TIMESTAMP>, "data": "sunny"},
|
|
269
|
-
{"id": <DAY3_TIMESTAMP>, "data": "sunny"},
|
|
270
|
-
{"id": <DAY4_TIMESTAMP>, "data": "sunny"},
|
|
271
|
-
{"id": <DAY5_TIMESTAMP>, "data": "rainy"}
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
]
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
"env": {
|
|
278
|
-
"account": "weather_provider",
|
|
279
|
-
"network": "testnet",
|
|
280
|
-
"no_cache": true,
|
|
281
|
-
"confirmed": true
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
> **Note**: Replace `<DAY1_TIMESTAMP>` through `<DAY5_TIMESTAMP>` with the actual values from Step 0.1. The `id` field is the timestamp (as a number) that keys the weather record — it must **exactly match** the timestamp you submit later in Step 7.3.
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## Step 1: Create Permission Object
|
|
292
|
-
|
|
293
|
-
Create a Permission object to manage access control for the travel service. Add permission indices for the travel_provider account.
|
|
294
|
-
|
|
295
|
-
**Prompt**: Create a Permission object named "travel_permission" for the travel service.
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"tool": "onchain_operations",
|
|
300
|
-
"data": {
|
|
301
|
-
"operation_type": "permission",
|
|
302
|
-
"data": {
|
|
303
|
-
"object": {
|
|
304
|
-
"name": "travel_permission",
|
|
305
|
-
"tags": ["travel", "iceland", "tourism"],
|
|
306
|
-
"replaceExistName": true
|
|
307
|
-
},
|
|
308
|
-
"description": "Permission for Iceland travel service",
|
|
309
|
-
"table": {
|
|
310
|
-
"op": "add perm by entity",
|
|
311
|
-
"entity": {"name_or_address": "travel_provider"},
|
|
312
|
-
"index": [1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 306]
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
"env": {
|
|
316
|
-
"account": "travel_provider",
|
|
317
|
-
"network": "testnet",
|
|
318
|
-
"no_cache": true,
|
|
319
|
-
"confirmed": true
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
> **Note**: Permission indices 1000-1009 are used for different workflow forwards. Index 306 is the built-in `SERVICE_MACHINE` permission — it governs binding or changing a Service's Machine (`service::machine_set`), and is required in Step 5 when the Service binds `travel_machine`. The travel_provider account is granted all these permissions.
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
## Step 1.5: Create Arbitration Permission
|
|
330
|
-
|
|
331
|
-
Create a dedicated Permission object for the Arbitration. No special permission indices are needed — the creator (`travel_provider`) automatically becomes the admin of the new Permission.
|
|
332
|
-
|
|
333
|
-
> **Why a separate Permission?** When a Service binds an Arbitration, the contract asserts `arbitration.permission != service.permission` (error `E_ARBITRATION_PERMISSION_CONFLICT`). If the Arbitration shared the Service's permission, the Service owner would control dispute resolution, breaking fairness. The Arbitration must therefore use an independent Permission object.
|
|
334
|
-
|
|
335
|
-
**Prompt**: Create a Permission object named "travel_arbitration_permission" for the arbitration.
|
|
336
|
-
|
|
337
|
-
```json
|
|
338
|
-
{
|
|
339
|
-
"tool": "onchain_operations",
|
|
340
|
-
"data": {
|
|
341
|
-
"operation_type": "permission",
|
|
342
|
-
"data": {
|
|
343
|
-
"object": {
|
|
344
|
-
"name": "travel_arbitration_permission",
|
|
345
|
-
"tags": ["travel", "arbitration"],
|
|
346
|
-
"replaceExistName": true
|
|
347
|
-
},
|
|
348
|
-
"description": "Independent permission for travel arbitration (must differ from the Service permission)"
|
|
349
|
-
},
|
|
350
|
-
"env": {
|
|
351
|
-
"account": "travel_provider",
|
|
352
|
-
"network": "testnet",
|
|
353
|
-
"no_cache": true,
|
|
354
|
-
"confirmed": true
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## Step 2: Create Arbitration Object
|
|
363
|
-
|
|
364
|
-
Create an Arbitration object for dispute resolution. It uses the independent `travel_arbitration_permission` created in Step 1.5.
|
|
365
|
-
|
|
366
|
-
**Prompt**: Create an Arbitration named "travel_arbitration".
|
|
367
|
-
|
|
368
|
-
```json
|
|
369
|
-
{
|
|
370
|
-
"tool": "onchain_operations",
|
|
371
|
-
"data": {
|
|
372
|
-
"operation_type": "arbitration",
|
|
373
|
-
"data": {
|
|
374
|
-
"object": {
|
|
375
|
-
"name": "travel_arbitration",
|
|
376
|
-
"permission": "travel_arbitration_permission",
|
|
377
|
-
"replaceExistName": true
|
|
378
|
-
},
|
|
379
|
-
"description": "Arbitration for Iceland travel service disputes"
|
|
380
|
-
},
|
|
381
|
-
"env": {
|
|
382
|
-
"account": "travel_provider",
|
|
383
|
-
"network": "testnet",
|
|
384
|
-
"no_cache": true,
|
|
385
|
-
"confirmed": true
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Step 2.5: Create Service (Unpublished)
|
|
394
|
-
|
|
395
|
-
Create the Service without publishing to obtain its address for Guard creation. The Service address is required by the allocator Guards (Step 3.4–3.6) to verify `order.service == travel_service` (R-C3-05 cross-service theft protection).
|
|
396
|
-
|
|
397
|
-
**Prompt**: Create Service "travel_service" with permission "travel_permission", do not publish.
|
|
398
|
-
|
|
399
|
-
```json
|
|
400
|
-
{
|
|
401
|
-
"tool": "onchain_operations",
|
|
402
|
-
"data": {
|
|
403
|
-
"operation_type": "service",
|
|
404
|
-
"data": {
|
|
405
|
-
"object": {
|
|
406
|
-
"name": "travel_service",
|
|
407
|
-
"permission": "travel_permission",
|
|
408
|
-
"replaceExistName": true
|
|
409
|
-
},
|
|
410
|
-
"description": "Iceland travel service: Blue Lagoon SPA + Glacier Ice Scooting.",
|
|
411
|
-
"pause": false
|
|
412
|
-
},
|
|
413
|
-
"env": {
|
|
414
|
-
"account": "travel_provider",
|
|
415
|
-
"network": "testnet",
|
|
416
|
-
"no_cache": true,
|
|
417
|
-
"confirmed": true
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
**Record the Service address** - it will be needed for allocator Guard creation (Step 3.4–3.6) to bind `order.service` verification.
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
### Step 2.6: Create Treasury (Merchant Revenue Aggregation)
|
|
428
|
-
|
|
429
|
-
Create a Treasury object to aggregate merchant revenue. The Treasury uses the **same Permission** as the Service (`travel_permission`) for consistency — a single permission organization governs both fund collection and service operations.
|
|
430
|
-
|
|
431
|
-
**Prompt**: Create Treasury "travel_treasury" with permission "travel_permission", type parameter "0x2::wow::WOW".
|
|
432
|
-
|
|
433
|
-
```json
|
|
434
|
-
{
|
|
435
|
-
"tool": "onchain_operations",
|
|
436
|
-
"data": {
|
|
437
|
-
"operation_type": "treasury",
|
|
438
|
-
"data": {
|
|
439
|
-
"object": {
|
|
440
|
-
"name": "travel_treasury",
|
|
441
|
-
"type_parameter": "0x2::wow::WOW",
|
|
442
|
-
"permission": "travel_permission",
|
|
443
|
-
"replaceExistName": true
|
|
444
|
-
},
|
|
445
|
-
"description": "Treasury for aggregating travel service merchant revenue. Uses the same Permission as the Service (travel_permission) for consistency — a single permission organization governs both fund collection and service operations."
|
|
446
|
-
},
|
|
447
|
-
"env": {
|
|
448
|
-
"account": "travel_provider",
|
|
449
|
-
"network": "testnet",
|
|
450
|
-
"no_cache": true,
|
|
451
|
-
"confirmed": true
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
> **Treasury-First Rule**: Following the fund-flow design pattern established in the Insurance and MyShop_Advanced examples, merchant revenue flows to `travel_treasury` (not directly to the Service address). This:
|
|
458
|
-
> 1. **Aggregates public funds** for operational distribution and accounting
|
|
459
|
-
> 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 Guard
|
|
460
|
-
> 3. **Uses permission consistency** — Treasury and Service share `travel_permission`, ensuring unified governance
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
## Step 3: Create Guards
|
|
465
|
-
|
|
466
|
-
Create all Guards needed for the workflow and fund allocation. Guards are immutable once created, so create them before the Machine and Service.
|
|
467
|
-
|
|
468
|
-
> **Prerequisite**: The Service must be created (unpublished) in Step 2.5 first, because the allocator Guards (3.4–3.6) reference the Service address to verify `order.service == travel_service` (R-C3-05 cross-service theft protection).
|
|
469
|
-
|
|
470
|
-
### 3.1 Weather Check Guard
|
|
471
|
-
|
|
472
|
-
Creates a Guard that verifies weather data exists for a given date. This Guard is bound to the "go_ice_scooting" forward (entering the weather-dependent Ice Scooting activity) to ensure the activity date has a weather record in the repository.
|
|
473
|
-
|
|
474
|
-
**Guard Logic**:
|
|
475
|
-
```
|
|
476
|
-
repository.data has("Condition", convert_number_address(activity_date))
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
**Prompt**: Create a Guard named "weather_check_guard" for weather condition verification.
|
|
480
|
-
|
|
481
|
-
```json
|
|
482
|
-
{
|
|
483
|
-
"tool": "onchain_operations",
|
|
484
|
-
"data": {
|
|
485
|
-
"operation_type": "guard",
|
|
486
|
-
"data": {
|
|
487
|
-
"namedNew": {
|
|
488
|
-
"name": "weather_check_guard",
|
|
489
|
-
"tags": ["weather", "check", "travel"],
|
|
490
|
-
"replaceExistName": true
|
|
491
|
-
},
|
|
492
|
-
"description": "Weather check guard for ice scooting activity. Checks if weather data exists for the activity date.",
|
|
493
|
-
"table": [
|
|
494
|
-
{
|
|
495
|
-
"identifier": 0,
|
|
496
|
-
"b_submission": false,
|
|
497
|
-
"value_type": "Address",
|
|
498
|
-
"value": "weather_repo",
|
|
499
|
-
"name": "Weather Repository address"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
"identifier": 1,
|
|
503
|
-
"b_submission": false,
|
|
504
|
-
"value_type": "String",
|
|
505
|
-
"value": "Condition",
|
|
506
|
-
"name": "Repository policy name"
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
"identifier": 2,
|
|
510
|
-
"b_submission": true,
|
|
511
|
-
"value_type": "U64",
|
|
512
|
-
"name": "Activity date timestamp (submitted at runtime)"
|
|
513
|
-
}
|
|
514
|
-
],
|
|
515
|
-
"root": {
|
|
516
|
-
"type": "query",
|
|
517
|
-
"query": "repository.data has",
|
|
518
|
-
"object": {"identifier": 0},
|
|
519
|
-
"parameters": [
|
|
520
|
-
{"type": "identifier", "identifier": 1},
|
|
521
|
-
{
|
|
522
|
-
"type": "convert_number_address",
|
|
523
|
-
"node": {"type": "identifier", "identifier": 2}
|
|
524
|
-
}
|
|
525
|
-
]
|
|
526
|
-
}
|
|
527
|
-
},
|
|
528
|
-
"env": {
|
|
529
|
-
"account": "travel_provider",
|
|
530
|
-
"network": "testnet",
|
|
531
|
-
"no_cache": true,
|
|
532
|
-
"confirmed": true
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
**Guard Table**:
|
|
539
|
-
|
|
540
|
-
| identifier | b_submission | value_type | value | Purpose |
|
|
541
|
-
|------------|-------------|-----------|-------|---------|
|
|
542
|
-
| 0 | false | Address | weather_repo | Weather Repository to query |
|
|
543
|
-
| 1 | false | String | "Condition" | Repository policy name |
|
|
544
|
-
| 2 | **true** | U64 | 0 (placeholder) | Activity date timestamp, submitted at runtime |
|
|
545
|
-
|
|
546
|
-
### 3.2 Travel Complete Guard (Time-Lock)
|
|
547
|
-
|
|
548
|
-
Creates a Guard that verifies the time-lock condition for order completion.
|
|
549
|
-
|
|
550
|
-
**Guard Logic**:
|
|
551
|
-
```
|
|
552
|
-
clock > progress.current_time + 1000
|
|
553
|
-
(progress accessed via Order + convert_witness="OrderProgress")
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
**Prompt**: Create a Guard named "travel_complete_guard" for time-lock verification.
|
|
557
|
-
|
|
558
|
-
```json
|
|
559
|
-
{
|
|
560
|
-
"tool": "onchain_operations",
|
|
561
|
-
"data": {
|
|
562
|
-
"operation_type": "guard",
|
|
563
|
-
"data": {
|
|
564
|
-
"namedNew": {
|
|
565
|
-
"name": "travel_complete_guard",
|
|
566
|
-
"tags": ["travel", "time-lock", "complete"],
|
|
567
|
-
"replaceExistName": true
|
|
568
|
-
},
|
|
569
|
-
"description": "Time-lock guard for travel order completion. Requires current clock > progress.current_time + 1000ms.",
|
|
570
|
-
"table": [
|
|
571
|
-
{
|
|
572
|
-
"identifier": 0,
|
|
573
|
-
"b_submission": true,
|
|
574
|
-
"value_type": "Address",
|
|
575
|
-
"name": "Order ID (submitted at runtime)"
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
"identifier": 1,
|
|
579
|
-
"b_submission": false,
|
|
580
|
-
"value_type": "U64",
|
|
581
|
-
"value": 1000,
|
|
582
|
-
"name": "Time-lock duration in ms"
|
|
583
|
-
}
|
|
584
|
-
],
|
|
585
|
-
"root": {
|
|
586
|
-
"type": "logic_as_u256_greater",
|
|
587
|
-
"nodes": [
|
|
588
|
-
{"type": "context", "context": "Clock"},
|
|
589
|
-
{
|
|
590
|
-
"type": "calc_number_add",
|
|
591
|
-
"nodes": [
|
|
592
|
-
{
|
|
593
|
-
"type": "query",
|
|
594
|
-
"query": "progress.current_time",
|
|
595
|
-
"object": {"identifier": 0, "convert_witness": "OrderProgress"},
|
|
596
|
-
"parameters": []
|
|
597
|
-
},
|
|
598
|
-
{"type": "identifier", "identifier": 1}
|
|
599
|
-
]
|
|
600
|
-
}
|
|
601
|
-
]
|
|
602
|
-
}
|
|
603
|
-
},
|
|
604
|
-
"env": {
|
|
605
|
-
"account": "travel_provider",
|
|
606
|
-
"network": "testnet",
|
|
607
|
-
"no_cache": true,
|
|
608
|
-
"confirmed": true
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
**Guard Table**:
|
|
615
|
-
|
|
616
|
-
| identifier | b_submission | value_type | value | Purpose |
|
|
617
|
-
|------------|-------------|-----------|-------|---------|
|
|
618
|
-
| 0 | **true** | Address | 0x0...0 (placeholder) | Order ID submitted at runtime, converted to Progress via convert_witness |
|
|
619
|
-
| 1 | false | U64 | 1000 | Time-lock duration in ms (1 second for testing) |
|
|
620
|
-
|
|
621
|
-
> **Important**: `1000` ms (1 second) is for testing only. In production, set to a reasonable duration (e.g., 8 hours = 28800000 ms).
|
|
622
|
-
|
|
623
|
-
### 3.3 Travel Cancel Guard
|
|
624
|
-
|
|
625
|
-
Creates a Guard that allows order cancellation. This Guard always passes (returns true), as cancellation is controlled by permission-based access.
|
|
626
|
-
|
|
627
|
-
**Prompt**: Create a Guard named "travel_cancel_guard" for order cancellation.
|
|
628
|
-
|
|
629
|
-
```json
|
|
630
|
-
{
|
|
631
|
-
"tool": "onchain_operations",
|
|
632
|
-
"data": {
|
|
633
|
-
"operation_type": "guard",
|
|
634
|
-
"data": {
|
|
635
|
-
"namedNew": {
|
|
636
|
-
"name": "travel_cancel_guard",
|
|
637
|
-
"tags": ["travel", "cancel"],
|
|
638
|
-
"replaceExistName": true
|
|
639
|
-
},
|
|
640
|
-
"description": "Cancel guard for travel orders. Always passes - cancellation is controlled by permission-based access.",
|
|
641
|
-
"table": [
|
|
642
|
-
{
|
|
643
|
-
"identifier": 0,
|
|
644
|
-
"b_submission": false,
|
|
645
|
-
"value_type": "Bool",
|
|
646
|
-
"value": true,
|
|
647
|
-
"name": "Always true"
|
|
648
|
-
}
|
|
649
|
-
],
|
|
650
|
-
"root": {
|
|
651
|
-
"type": "identifier",
|
|
652
|
-
"identifier": 0
|
|
653
|
-
}
|
|
654
|
-
},
|
|
655
|
-
"env": {
|
|
656
|
-
"account": "travel_provider",
|
|
657
|
-
"network": "testnet",
|
|
658
|
-
"no_cache": true,
|
|
659
|
-
"confirmed": true
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### 3.4 Merchant Victory Guard (100% to Treasury)
|
|
666
|
-
|
|
667
|
-
Checks if order progress current node is "Complete" AND order belongs to this service. If passed, merchant (Treasury) receives 100% of funds.
|
|
668
|
-
|
|
669
|
-
**Prompt**: Create Guard "merchant_victory_guard".
|
|
670
|
-
|
|
671
|
-
```json
|
|
672
|
-
{
|
|
673
|
-
"tool": "onchain_operations",
|
|
674
|
-
"data": {
|
|
675
|
-
"operation_type": "guard",
|
|
676
|
-
"data": {
|
|
677
|
-
"namedNew": {
|
|
678
|
-
"name": "merchant_victory_guard",
|
|
679
|
-
"tags": ["merchant", "victory", "complete", "level3-scene-combined"],
|
|
680
|
-
"replaceExistName": true
|
|
681
|
-
},
|
|
682
|
-
"description": "Guard for merchant victory: checks if order progress current node is Complete AND order belongs to travel_service. VERIFIER CONSTRAINT LEVEL 3 (scene-combined): No Signer binding needed because the allocator uses sharing.who=Entity (travel_treasury) — funds always flow to the Treasury regardless of caller (R-C3-06 safe). Two-fold verification: (1) order at Complete node, (2) order belongs to this service (prevents cross-service theft, R-C3-05).",
|
|
683
|
-
"table": [
|
|
684
|
-
{
|
|
685
|
-
"identifier": 0,
|
|
686
|
-
"b_submission": true,
|
|
687
|
-
"value_type": "Address",
|
|
688
|
-
"name": "Order ID (submitted at runtime)"
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
"identifier": 1,
|
|
692
|
-
"b_submission": false,
|
|
693
|
-
"value_type": "String",
|
|
694
|
-
"value": "Complete",
|
|
695
|
-
"name": "Complete node name"
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
"identifier": 2,
|
|
699
|
-
"b_submission": false,
|
|
700
|
-
"value_type": "Address",
|
|
701
|
-
"value": "travel_service",
|
|
702
|
-
"name": "Service address (this service)"
|
|
703
|
-
}
|
|
704
|
-
],
|
|
705
|
-
"root": {
|
|
706
|
-
"type": "logic_and",
|
|
707
|
-
"nodes": [
|
|
708
|
-
{
|
|
709
|
-
"type": "logic_equal",
|
|
710
|
-
"nodes": [
|
|
711
|
-
{
|
|
712
|
-
"type": "query",
|
|
713
|
-
"query": "progress.current",
|
|
714
|
-
"object": {"identifier": 0, "convert_witness": "OrderProgress"},
|
|
715
|
-
"parameters": []
|
|
716
|
-
},
|
|
717
|
-
{"type": "identifier", "identifier": 1}
|
|
718
|
-
]
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
"type": "logic_equal",
|
|
722
|
-
"nodes": [
|
|
723
|
-
{"type": "query", "query": "order.service", "object": {"identifier": 0}, "parameters": []},
|
|
724
|
-
{"type": "identifier", "identifier": 2}
|
|
725
|
-
]
|
|
726
|
-
}
|
|
727
|
-
]
|
|
728
|
-
}
|
|
729
|
-
},
|
|
730
|
-
"env": {
|
|
731
|
-
"account": "travel_provider",
|
|
732
|
-
"network": "testnet",
|
|
733
|
-
"no_cache": true,
|
|
734
|
-
"confirmed": true
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
**Guard Explanation (Two-fold Verification — Level 3 Scene-Combined):**
|
|
741
|
-
- **Table Item 0**: Order address (submitted at runtime)
|
|
742
|
-
- **Table Item 1**: Constant string "Complete" (merchant win node name)
|
|
743
|
-
- **Table Item 2**: Constant address `travel_service` (this service's on-chain address)
|
|
744
|
-
- **Condition 1 — Complete Node**: `logic_equal[query(progress.current, witness="OrderProgress"), identifier[1]]` — verifies the order is at the Complete node
|
|
745
|
-
- **Condition 2 — Service Ownership**: `logic_equal[query("order.service"), identifier[2]]` — verifies the submitted Order's `service` field equals `travel_service`, **preventing cross-service theft** where someone submits another service's order (R-C3-05)
|
|
746
|
-
- **root**: `logic_and` of both conditions — all must pass for allocation to proceed
|
|
747
|
-
|
|
748
|
-
> **Risk Elimination (R-C3-05 + R-C3-06) — Level 3 Scene-Combined Design**:
|
|
749
|
-
> - **R-C3-05 (Cross-service theft)**: Eliminated by the Service Ownership check (Condition 2). An attacker cannot submit another service's order because `order.service` won't match `travel_service`.
|
|
750
|
-
> - **R-C3-06 (Fund theft via Signer)**: Eliminated by the scene itself — the allocator uses `"who": {"Entity": {"name_or_address": "travel_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.
|
|
751
|
-
|
|
752
|
-
### 3.5 No Ice Scooting Guard (80% Treasury, 20% Refund)
|
|
753
|
-
|
|
754
|
-
Checks if progress current is "Cancel" or "Ice Scooting" AND order belongs to this service. If passed, merchant (Treasury) gets 80%, user gets 20% refund.
|
|
755
|
-
|
|
756
|
-
**Prompt**: Create Guard "no_ice_scooting_guard".
|
|
757
|
-
|
|
758
|
-
```json
|
|
759
|
-
{
|
|
760
|
-
"tool": "onchain_operations",
|
|
761
|
-
"data": {
|
|
762
|
-
"operation_type": "guard",
|
|
763
|
-
"data": {
|
|
764
|
-
"namedNew": {
|
|
765
|
-
"name": "no_ice_scooting_guard",
|
|
766
|
-
"tags": ["user", "cancel", "ice_scooting", "level3-scene-combined"],
|
|
767
|
-
"replaceExistName": true
|
|
768
|
-
},
|
|
769
|
-
"description": "Guard for user not participating in ice scooting: checks if progress current is Cancel or Ice Scooting AND order belongs to travel_service. VERIFIER CONSTRAINT LEVEL 3 (scene-combined): No Signer binding needed because the allocator uses sharing.who=Entity (travel_treasury) for merchant portion and sharing.who=GuardIdentifier(0) for customer refund (escrow to Order address). Two-fold verification: (1) order at Cancel/Ice Scooting node, (2) order belongs to this service (prevents cross-service theft, R-C3-05).",
|
|
770
|
-
"table": [
|
|
771
|
-
{
|
|
772
|
-
"identifier": 0,
|
|
773
|
-
"b_submission": true,
|
|
774
|
-
"value_type": "Address",
|
|
775
|
-
"name": "Order ID (submitted at runtime)"
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
"identifier": 1,
|
|
779
|
-
"b_submission": false,
|
|
780
|
-
"value_type": "String",
|
|
781
|
-
"value": "Cancel",
|
|
782
|
-
"name": "Cancel node name"
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
"identifier": 2,
|
|
786
|
-
"b_submission": false,
|
|
787
|
-
"value_type": "String",
|
|
788
|
-
"value": "Ice Scooting",
|
|
789
|
-
"name": "Ice Scooting node name"
|
|
790
|
-
},
|
|
791
|
-
{
|
|
792
|
-
"identifier": 3,
|
|
793
|
-
"b_submission": false,
|
|
794
|
-
"value_type": "Address",
|
|
795
|
-
"value": "travel_service",
|
|
796
|
-
"name": "Service address (this service)"
|
|
797
|
-
}
|
|
798
|
-
],
|
|
799
|
-
"root": {
|
|
800
|
-
"type": "logic_and",
|
|
801
|
-
"nodes": [
|
|
802
|
-
{
|
|
803
|
-
"type": "logic_or",
|
|
804
|
-
"nodes": [
|
|
805
|
-
{
|
|
806
|
-
"type": "logic_equal",
|
|
807
|
-
"nodes": [
|
|
808
|
-
{
|
|
809
|
-
"type": "query",
|
|
810
|
-
"query": "progress.current",
|
|
811
|
-
"object": {"identifier": 0, "convert_witness": "OrderProgress"},
|
|
812
|
-
"parameters": []
|
|
813
|
-
},
|
|
814
|
-
{"type": "identifier", "identifier": 1}
|
|
815
|
-
]
|
|
816
|
-
},
|
|
817
|
-
{
|
|
818
|
-
"type": "logic_equal",
|
|
819
|
-
"nodes": [
|
|
820
|
-
{
|
|
821
|
-
"type": "query",
|
|
822
|
-
"query": "progress.current",
|
|
823
|
-
"object": {"identifier": 0, "convert_witness": "OrderProgress"},
|
|
824
|
-
"parameters": []
|
|
825
|
-
},
|
|
826
|
-
{"type": "identifier", "identifier": 2}
|
|
827
|
-
]
|
|
828
|
-
}
|
|
829
|
-
]
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
"type": "logic_equal",
|
|
833
|
-
"nodes": [
|
|
834
|
-
{"type": "query", "query": "order.service", "object": {"identifier": 0}, "parameters": []},
|
|
835
|
-
{"type": "identifier", "identifier": 3}
|
|
836
|
-
]
|
|
837
|
-
}
|
|
838
|
-
]
|
|
839
|
-
}
|
|
840
|
-
},
|
|
841
|
-
"env": {
|
|
842
|
-
"account": "travel_provider",
|
|
843
|
-
"network": "testnet",
|
|
844
|
-
"no_cache": true,
|
|
845
|
-
"confirmed": true
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
**Guard Explanation (Two-fold Verification — Level 3 Scene-Combined):**
|
|
852
|
-
- **Table Item 0**: Order address (submitted at runtime)
|
|
853
|
-
- **Table Items 1-2**: Constant strings "Cancel" and "Ice Scooting" (node names)
|
|
854
|
-
- **Table Item 3**: Constant address `travel_service` (this service's on-chain address)
|
|
855
|
-
- **Condition 1 — Cancel/Ice Scooting Node**: `logic_or` of two `logic_equal` checks against `query(progress.current, witness="OrderProgress")` — verifies the order is at Cancel or Ice Scooting node
|
|
856
|
-
- **Condition 2 — Service Ownership**: `logic_equal[query("order.service"), identifier[3]]` — verifies the submitted Order's `service` field equals `travel_service`, **preventing cross-service theft** (R-C3-05)
|
|
857
|
-
- **root**: `logic_and` of both conditions — all must pass for allocation to proceed
|
|
858
|
-
|
|
859
|
-
> **Risk Elimination (R-C3-05 + R-C3-06) — Level 3 Scene-Combined Design**:
|
|
860
|
-
> - **R-C3-05 (Cross-service theft)**: Eliminated by the Service Ownership check (Condition 2). An attacker cannot submit another service's order because `order.service` won't match `travel_service`.
|
|
861
|
-
> - **R-C3-06 (Fund theft via Signer)**: Eliminated by the scene itself — the merchant portion uses `"who": {"Entity": {"name_or_address": "travel_treasury"}}` (funds flow to the fixed Treasury address), and the customer refund uses `"who": {"GuardIdentifier": 0}` (funds flow to the Order object's address as escrow). Neither portion flows to the caller's wallet, so **no Signer binding is needed**.
|
|
862
|
-
|
|
863
|
-
### 3.6 No SPA Guard (5% Treasury, 95% Refund)
|
|
864
|
-
|
|
865
|
-
Checks if progress current is "SPA" AND order belongs to this service. If passed, merchant (Treasury) gets 5%, user gets 95% refund.
|
|
866
|
-
|
|
867
|
-
**Prompt**: Create Guard "no_spa_guard".
|
|
868
|
-
|
|
869
|
-
```json
|
|
870
|
-
{
|
|
871
|
-
"tool": "onchain_operations",
|
|
872
|
-
"data": {
|
|
873
|
-
"operation_type": "guard",
|
|
874
|
-
"data": {
|
|
875
|
-
"namedNew": {
|
|
876
|
-
"name": "no_spa_guard",
|
|
877
|
-
"tags": ["user", "cancel", "spa", "level3-scene-combined"],
|
|
878
|
-
"replaceExistName": true
|
|
879
|
-
},
|
|
880
|
-
"description": "Guard for user not participating in SPA: checks if progress current is SPA AND order belongs to travel_service. VERIFIER CONSTRAINT LEVEL 3 (scene-combined): No Signer binding needed because the allocator uses sharing.who=Entity (travel_treasury) for merchant portion and sharing.who=GuardIdentifier(0) for customer refund (escrow to Order address). Two-fold verification: (1) order at SPA node, (2) order belongs to this service (prevents cross-service theft, R-C3-05).",
|
|
881
|
-
"table": [
|
|
882
|
-
{
|
|
883
|
-
"identifier": 0,
|
|
884
|
-
"b_submission": true,
|
|
885
|
-
"value_type": "Address",
|
|
886
|
-
"name": "Order ID (submitted at runtime)"
|
|
887
|
-
},
|
|
888
|
-
{
|
|
889
|
-
"identifier": 1,
|
|
890
|
-
"b_submission": false,
|
|
891
|
-
"value_type": "String",
|
|
892
|
-
"value": "SPA",
|
|
893
|
-
"name": "SPA node name"
|
|
894
|
-
},
|
|
895
|
-
{
|
|
896
|
-
"identifier": 2,
|
|
897
|
-
"b_submission": false,
|
|
898
|
-
"value_type": "Address",
|
|
899
|
-
"value": "travel_service",
|
|
900
|
-
"name": "Service address (this service)"
|
|
901
|
-
}
|
|
902
|
-
],
|
|
903
|
-
"root": {
|
|
904
|
-
"type": "logic_and",
|
|
905
|
-
"nodes": [
|
|
906
|
-
{
|
|
907
|
-
"type": "logic_equal",
|
|
908
|
-
"nodes": [
|
|
909
|
-
{
|
|
910
|
-
"type": "query",
|
|
911
|
-
"query": "progress.current",
|
|
912
|
-
"object": {"identifier": 0, "convert_witness": "OrderProgress"},
|
|
913
|
-
"parameters": []
|
|
914
|
-
},
|
|
915
|
-
{"type": "identifier", "identifier": 1}
|
|
916
|
-
]
|
|
917
|
-
},
|
|
918
|
-
{
|
|
919
|
-
"type": "logic_equal",
|
|
920
|
-
"nodes": [
|
|
921
|
-
{"type": "query", "query": "order.service", "object": {"identifier": 0}, "parameters": []},
|
|
922
|
-
{"type": "identifier", "identifier": 2}
|
|
923
|
-
]
|
|
924
|
-
}
|
|
925
|
-
]
|
|
926
|
-
}
|
|
927
|
-
},
|
|
928
|
-
"env": {
|
|
929
|
-
"account": "travel_provider",
|
|
930
|
-
"network": "testnet",
|
|
931
|
-
"no_cache": true,
|
|
932
|
-
"confirmed": true
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
**Guard Explanation (Two-fold Verification — Level 3 Scene-Combined):**
|
|
939
|
-
- **Table Item 0**: Order address (submitted at runtime)
|
|
940
|
-
- **Table Item 1**: Constant string "SPA" (node name)
|
|
941
|
-
- **Table Item 2**: Constant address `travel_service` (this service's on-chain address)
|
|
942
|
-
- **Condition 1 — SPA Node**: `logic_equal[query(progress.current, witness="OrderProgress"), identifier[1]]` — verifies the order is at the SPA node
|
|
943
|
-
- **Condition 2 — Service Ownership**: `logic_equal[query("order.service"), identifier[2]]` — verifies the submitted Order's `service` field equals `travel_service`, **preventing cross-service theft** (R-C3-05)
|
|
944
|
-
- **root**: `logic_and` of both conditions — all must pass for allocation to proceed
|
|
945
|
-
|
|
946
|
-
> **Risk Elimination (R-C3-05 + R-C3-06) — Level 3 Scene-Combined Design**:
|
|
947
|
-
> - **R-C3-05 (Cross-service theft)**: Eliminated by the Service Ownership check (Condition 2). An attacker cannot submit another service's order because `order.service` won't match `travel_service`.
|
|
948
|
-
> - **R-C3-06 (Fund theft via Signer)**: Eliminated by the scene itself — the merchant portion uses `"who": {"Entity": {"name_or_address": "travel_treasury"}}` (funds flow to the fixed Treasury address), and the customer refund uses `"who": {"GuardIdentifier": 0}` (funds flow to the Order object's address as escrow). Neither portion flows to the caller's wallet, so **no Signer binding is needed**.
|
|
949
|
-
|
|
950
|
-
---
|
|
951
|
-
|
|
952
|
-
## Step 4: Create and Publish Machine
|
|
953
|
-
|
|
954
|
-
Create a Machine to define the travel service workflow with all nodes and forwards. The Machine must be published before it can be bound to a Service.
|
|
955
|
-
|
|
956
|
-
> **Important**: In the Machine node structure, each node's `pairs` define how to ENTER that node. The `prev_node` specifies the source node, and `forwards` are the operation names used to advance from `prev_node` to this node.
|
|
957
|
-
|
|
958
|
-
**Prompt**: Create and publish a Machine named "travel_machine" with the complete travel workflow.
|
|
959
|
-
|
|
960
|
-
```json
|
|
961
|
-
{
|
|
962
|
-
"tool": "onchain_operations",
|
|
963
|
-
"data": {
|
|
964
|
-
"operation_type": "machine",
|
|
965
|
-
"data": {
|
|
966
|
-
"object": {
|
|
967
|
-
"name": "travel_machine",
|
|
968
|
-
"permission": "travel_permission",
|
|
969
|
-
"replaceExistName": true
|
|
970
|
-
},
|
|
971
|
-
"description": "Iceland travel service workflow: (init) -> Buy Insurance -> SPA -> Ice Scooting -> Complete/Cancel",
|
|
972
|
-
"node": {
|
|
973
|
-
"op": "add",
|
|
974
|
-
"bReplace": true,
|
|
975
|
-
"nodes": [
|
|
976
|
-
{
|
|
977
|
-
"name": "Buy Insurance",
|
|
978
|
-
"pairs": [
|
|
979
|
-
{
|
|
980
|
-
"prev_node": "",
|
|
981
|
-
"threshold": 1,
|
|
982
|
-
"forwards": [
|
|
983
|
-
{
|
|
984
|
-
"name": "buy_insurance",
|
|
985
|
-
"weight": 1,
|
|
986
|
-
"permissionIndex": 1000
|
|
987
|
-
}
|
|
988
|
-
]
|
|
989
|
-
}
|
|
990
|
-
]
|
|
991
|
-
},
|
|
992
|
-
{
|
|
993
|
-
"name": "SPA",
|
|
994
|
-
"pairs": [
|
|
995
|
-
{
|
|
996
|
-
"prev_node": "Buy Insurance",
|
|
997
|
-
"threshold": 1,
|
|
998
|
-
"forwards": [
|
|
999
|
-
{
|
|
1000
|
-
"name": "go_spa",
|
|
1001
|
-
"weight": 1,
|
|
1002
|
-
"permissionIndex": 1001
|
|
1003
|
-
}
|
|
1004
|
-
]
|
|
1005
|
-
}
|
|
1006
|
-
]
|
|
1007
|
-
},
|
|
1008
|
-
{
|
|
1009
|
-
"name": "Ice Scooting",
|
|
1010
|
-
"pairs": [
|
|
1011
|
-
{
|
|
1012
|
-
"prev_node": "SPA",
|
|
1013
|
-
"threshold": 1,
|
|
1014
|
-
"forwards": [
|
|
1015
|
-
{
|
|
1016
|
-
"name": "go_ice_scooting",
|
|
1017
|
-
"weight": 1,
|
|
1018
|
-
"permissionIndex": 1004,
|
|
1019
|
-
"guard": {
|
|
1020
|
-
"guard": "weather_check_guard",
|
|
1021
|
-
"retained_submission": []
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
]
|
|
1025
|
-
}
|
|
1026
|
-
]
|
|
1027
|
-
},
|
|
1028
|
-
{
|
|
1029
|
-
"name": "Complete",
|
|
1030
|
-
"pairs": [
|
|
1031
|
-
{
|
|
1032
|
-
"prev_node": "Ice Scooting",
|
|
1033
|
-
"threshold": 1,
|
|
1034
|
-
"forwards": [
|
|
1035
|
-
{
|
|
1036
|
-
"name": "complete_trip",
|
|
1037
|
-
"weight": 1,
|
|
1038
|
-
"permissionIndex": 1002,
|
|
1039
|
-
"guard": {
|
|
1040
|
-
"guard": "travel_complete_guard",
|
|
1041
|
-
"retained_submission": []
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
]
|
|
1045
|
-
}
|
|
1046
|
-
]
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
"name": "Cancel",
|
|
1050
|
-
"pairs": [
|
|
1051
|
-
{
|
|
1052
|
-
"prev_node": "Ice Scooting",
|
|
1053
|
-
"threshold": 1,
|
|
1054
|
-
"forwards": [
|
|
1055
|
-
{
|
|
1056
|
-
"name": "cancel_trip",
|
|
1057
|
-
"weight": 1,
|
|
1058
|
-
"permissionIndex": 1003,
|
|
1059
|
-
"guard": {
|
|
1060
|
-
"guard": "travel_cancel_guard",
|
|
1061
|
-
"retained_submission": []
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
]
|
|
1065
|
-
}
|
|
1066
|
-
]
|
|
1067
|
-
}
|
|
1068
|
-
]
|
|
1069
|
-
},
|
|
1070
|
-
"publish": true
|
|
1071
|
-
},
|
|
1072
|
-
"env": {
|
|
1073
|
-
"account": "travel_provider",
|
|
1074
|
-
"network": "testnet",
|
|
1075
|
-
"no_cache": true,
|
|
1076
|
-
"confirmed": true
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
**Machine Workflow Diagram**:
|
|
1083
|
-
|
|
1084
|
-
```
|
|
1085
|
-
("") --buy_insurance--> [Buy Insurance] --go_spa--> [SPA] --go_ice_scooting--[weather_check]--> [Ice Scooting]
|
|
1086
|
-
|
|
|
1087
|
-
+--complete_trip--[time-lock]--> [Complete]
|
|
1088
|
-
+--cancel_trip-------------> [Cancel]
|
|
1089
|
-
```
|
|
1090
|
-
|
|
1091
|
-
**Node Pair Structure**:
|
|
1092
|
-
|
|
1093
|
-
| Node | prev_node | Forward | Permission Index | Guard |
|
|
1094
|
-
|------|-----------|---------|-----------------|-------|
|
|
1095
|
-
| Buy Insurance | "" (entry) | buy_insurance | 1000 | - |
|
|
1096
|
-
| SPA | Buy Insurance | go_spa | 1001 | - |
|
|
1097
|
-
| Ice Scooting | SPA | go_ice_scooting | 1004 | weather_check_guard |
|
|
1098
|
-
| Complete | Ice Scooting | complete_trip | 1002 | travel_complete_guard |
|
|
1099
|
-
| Cancel | Ice Scooting | cancel_trip | 1003 | travel_cancel_guard |
|
|
1100
|
-
|
|
1101
|
-
> **Note**: The `prev_node` field uses an empty string `""` to denote the entry point. Each node defines how to enter it from a previous node. The `threshold` field (optional, defaults to `0`) specifies the minimum forward weight needed to trigger node advancement.
|
|
1102
|
-
|
|
1103
|
-
---
|
|
1104
|
-
|
|
1105
|
-
## Step 5: Configure and Publish Service
|
|
1106
|
-
|
|
1107
|
-
Configure the travel service (created unpublished in Step 2.5) with all bindings and publish it. The Service requires:
|
|
1108
|
-
- Machine binding (must be published first)
|
|
1109
|
-
- Sales items (product listing)
|
|
1110
|
-
- Arbitration binding
|
|
1111
|
-
- Order allocators (fund distribution rules — merchant funds flow to `travel_treasury`)
|
|
1112
|
-
- `pause: false` and `publish: true` to make it active
|
|
1113
|
-
|
|
1114
|
-
**Prompt**: Configure and publish the Service named "travel_service".
|
|
1115
|
-
|
|
1116
|
-
```json
|
|
1117
|
-
{
|
|
1118
|
-
"tool": "onchain_operations",
|
|
1119
|
-
"data": {
|
|
1120
|
-
"operation_type": "service",
|
|
1121
|
-
"data": {
|
|
1122
|
-
"object": "travel_service",
|
|
1123
|
-
"description": "Iceland travel service: Blue Lagoon SPA + Glacier Ice Scooting.",
|
|
1124
|
-
"machine": "travel_machine",
|
|
1125
|
-
"sales": {
|
|
1126
|
-
"op": "add",
|
|
1127
|
-
"sales": [
|
|
1128
|
-
{
|
|
1129
|
-
"name": "Iceland Travel Package",
|
|
1130
|
-
"price": "500000000",
|
|
1131
|
-
"stock": "99",
|
|
1132
|
-
"suspension": false,
|
|
1133
|
-
"wip": "",
|
|
1134
|
-
"wip_hash": ""
|
|
1135
|
-
}
|
|
1136
|
-
]
|
|
1137
|
-
},
|
|
1138
|
-
"arbitrations": {
|
|
1139
|
-
"op": "add",
|
|
1140
|
-
"objects": ["travel_arbitration"]
|
|
1141
|
-
},
|
|
1142
|
-
"order_allocators": {
|
|
1143
|
-
"description": "Travel order revenue allocation based on progress state",
|
|
1144
|
-
"threshold": 0,
|
|
1145
|
-
"allocators": [
|
|
1146
|
-
{
|
|
1147
|
-
"guard": "merchant_victory_guard",
|
|
1148
|
-
"sharing": [
|
|
1149
|
-
{
|
|
1150
|
-
"who": {"Entity": {"name_or_address": "travel_treasury"}},
|
|
1151
|
-
"sharing": 10000,
|
|
1152
|
-
"mode": "Rate"
|
|
1153
|
-
}
|
|
1154
|
-
]
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
"guard": "no_ice_scooting_guard",
|
|
1158
|
-
"sharing": [
|
|
1159
|
-
{
|
|
1160
|
-
"who": {"Entity": {"name_or_address": "travel_treasury"}},
|
|
1161
|
-
"sharing": 8000,
|
|
1162
|
-
"mode": "Rate"
|
|
1163
|
-
},
|
|
1164
|
-
{
|
|
1165
|
-
"who": {"GuardIdentifier": 0},
|
|
1166
|
-
"sharing": 2000,
|
|
1167
|
-
"mode": "Rate"
|
|
1168
|
-
}
|
|
1169
|
-
]
|
|
1170
|
-
},
|
|
1171
|
-
{
|
|
1172
|
-
"guard": "no_spa_guard",
|
|
1173
|
-
"sharing": [
|
|
1174
|
-
{
|
|
1175
|
-
"who": {"Entity": {"name_or_address": "travel_treasury"}},
|
|
1176
|
-
"sharing": 500,
|
|
1177
|
-
"mode": "Rate"
|
|
1178
|
-
},
|
|
1179
|
-
{
|
|
1180
|
-
"who": {"GuardIdentifier": 0},
|
|
1181
|
-
"sharing": 9500,
|
|
1182
|
-
"mode": "Rate"
|
|
1183
|
-
}
|
|
1184
|
-
]
|
|
1185
|
-
}
|
|
1186
|
-
]
|
|
1187
|
-
},
|
|
1188
|
-
"pause": false,
|
|
1189
|
-
"publish": true
|
|
1190
|
-
},
|
|
1191
|
-
"env": {
|
|
1192
|
-
"account": "travel_provider",
|
|
1193
|
-
"network": "testnet",
|
|
1194
|
-
"no_cache": true,
|
|
1195
|
-
"confirmed": true
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
```
|
|
1200
|
-
|
|
1201
|
-
> **Note**: `object` uses a plain string reference (`"travel_service"`) so this call **configures the draft Service created in Step 2.5**. Passing a full object definition block (`{name, permission, replaceExistName}`) here would create a brand-new Service object instead — orders and allocator Guard bindings pointing at the old draft would then fail validation.
|
|
1202
|
-
|
|
1203
|
-
**order_allocators Field Reference**:
|
|
1204
|
-
|
|
1205
|
-
| Field | Type | Description |
|
|
1206
|
-
|-------|------|-------------|
|
|
1207
|
-
| `description` | string | Description of the allocation strategy |
|
|
1208
|
-
| `threshold` | number | Minimum threshold for allocation (0 = no minimum) |
|
|
1209
|
-
| `allocators` | array | Array of allocator configurations |
|
|
1210
|
-
| `allocators[].guard` | string | Guard name/ID that must pass for this allocation |
|
|
1211
|
-
| `allocators[].fix` | string | Fixed amount per recipient ("0" = none) |
|
|
1212
|
-
| `allocators[].sharing` | array | Array of sharing rules |
|
|
1213
|
-
| `allocators[].sharing[].who` | object | Recipient: `{"Entity": {...}}` for fixed address, `{"GuardIdentifier": N}` for Guard table index, `{"Signer": "signer"}` for caller |
|
|
1214
|
-
| `allocators[].sharing[].sharing` | number | Amount in basis points (10000 = 100%) |
|
|
1215
|
-
| `allocators[].sharing[].mode` | string | Allocation mode ("Rate" or "Amount") |
|
|
1216
|
-
|
|
1217
|
-
**Recipient Types**:
|
|
1218
|
-
|
|
1219
|
-
| Type | Syntax | Resolves To | Use Case |
|
|
1220
|
-
|------|--------|-------------|----------|
|
|
1221
|
-
| `Entity` | `{"Entity": {"name_or_address": "travel_treasury"}}` | The named Treasury address | Merchant receipts (Treasury object — Treasury-first rule) |
|
|
1222
|
-
| `GuardIdentifier` | `{"GuardIdentifier": 0}` | Address from Guard table index 0 (submitted at runtime) | Customer refunds (Order ID submitted to Guard) |
|
|
1223
|
-
| `Signer` | `{"Signer": "signer"}` | The caller of `alloc_by_guard` | When caller should receive all funds (**⚠️ R-C3-06 Risk**: unsafe without Guard Signer binding) |
|
|
1224
|
-
|
|
1225
|
-
> **Important**: All allocation Guards (merchant_victory_guard, no_ice_scooting_guard, no_spa_guard) have `identifier: 0` as a submission field accepting the Order ID at runtime. `{"GuardIdentifier": 0}` resolves to this submitted Order ID, so funds are sent to the Order object — the customer (Order builder) can then withdraw.
|
|
1226
|
-
|
|
1227
|
-
> **Treasury-First Rule**: Merchant funds flow to `travel_treasury` (not `travel_service`) following the fund-flow design pattern. This makes all allocators inherently safe (R-C3-06) — funds go to a fixed Treasury regardless of caller, so no Signer binding is needed in the Guards. Combined with the R-C3-05 service ownership check in each Guard, the allocators are protected against both cross-service theft and fund theft via Signer.
|
|
1228
|
-
|
|
1229
|
-
**Allocation Logic**:
|
|
1230
|
-
|
|
1231
|
-
| Guard | Condition | Treasury Receives | Customer Receives | Verifier Level |
|
|
1232
|
-
|-------|-----------|-------------------|-------------------|----------------|
|
|
1233
|
-
| merchant_victory_guard | Progress is "Complete" + order.service verified | 100% (Entity → travel_treasury) | 0% | Level 3 (scene-combined) |
|
|
1234
|
-
| no_ice_scooting_guard | Progress is "Cancel" or "Ice Scooting" + order.service verified | 80% (Entity → travel_treasury) | 20% (GuardIdentifier: 0 → Order) | Level 3 (scene-combined) |
|
|
1235
|
-
| no_spa_guard | Progress is "SPA" + order.service verified | 5% (Entity → travel_treasury) | 95% (GuardIdentifier: 0 → Order) | Level 3 (scene-combined) |
|
|
1236
|
-
|
|
1237
|
-
---
|
|
1238
|
-
|
|
1239
|
-
## Step 6: Place Order (Customer Purchase)
|
|
1240
|
-
|
|
1241
|
-
The customer (Alice) purchases the travel package. This creates an Order, a Progress (initialized at the entry node ""), and an Allocation object automatically.
|
|
1242
|
-
|
|
1243
|
-
**Prompt**: Alice purchases the Iceland Travel Package from the service.
|
|
1244
|
-
|
|
1245
|
-
```json
|
|
1246
|
-
{
|
|
1247
|
-
"tool": "onchain_operations",
|
|
1248
|
-
"data": {
|
|
1249
|
-
"operation_type": "service",
|
|
1250
|
-
"data": {
|
|
1251
|
-
"object": "travel_service",
|
|
1252
|
-
"order_new": {
|
|
1253
|
-
"buy": {
|
|
1254
|
-
"items": [
|
|
1255
|
-
{
|
|
1256
|
-
"name": "Iceland Travel Package",
|
|
1257
|
-
"stock": "1",
|
|
1258
|
-
"wip_hash": ""
|
|
1259
|
-
}
|
|
1260
|
-
],
|
|
1261
|
-
"total_pay": {"balance": "500000000"}
|
|
1262
|
-
},
|
|
1263
|
-
"namedNewOrder": {
|
|
1264
|
-
"name": "alice_travel_order",
|
|
1265
|
-
"replaceExistName": true
|
|
1266
|
-
},
|
|
1267
|
-
"namedNewProgress": {
|
|
1268
|
-
"name": "alice_travel_progress",
|
|
1269
|
-
"replaceExistName": true
|
|
1270
|
-
},
|
|
1271
|
-
"namedNewAllocation": {
|
|
1272
|
-
"name": "alice_travel_allocation",
|
|
1273
|
-
"replaceExistName": true
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
},
|
|
1277
|
-
"env": {
|
|
1278
|
-
"account": "alice",
|
|
1279
|
-
"network": "testnet",
|
|
1280
|
-
"no_cache": true,
|
|
1281
|
-
"confirmed": true
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
```
|
|
1286
|
-
|
|
1287
|
-
> **Note**: The `total_pay.balance` of `500000000` equals 0.5 WOW (1 WOW = 10^9 base units). The customer account must have sufficient balance. The operation creates three named objects: Order, Progress, and Allocation.
|
|
1288
|
-
|
|
1289
|
-
---
|
|
1290
|
-
|
|
1291
|
-
## Step 7: Progress Operations (Order Workflow)
|
|
1292
|
-
|
|
1293
|
-
The service provider advances the Progress through the workflow nodes. Each operation moves the Progress from the current node to the next node via a specified forward.
|
|
1294
|
-
|
|
1295
|
-
> **Important**: Add `"no_cache": true` to the `env` field for all Progress operations to avoid stale cache issues.
|
|
1296
|
-
|
|
1297
|
-
### 7.1 Progress to Buy Insurance
|
|
1298
|
-
|
|
1299
|
-
Move from initial node ("") to "Buy Insurance" node.
|
|
1300
|
-
|
|
1301
|
-
**Prompt**: Operate progress to move to Buy Insurance node.
|
|
1302
|
-
|
|
1303
|
-
```json
|
|
1304
|
-
{
|
|
1305
|
-
"tool": "onchain_operations",
|
|
1306
|
-
"data": {
|
|
1307
|
-
"operation_type": "progress",
|
|
1308
|
-
"data": {
|
|
1309
|
-
"object": "alice_travel_progress",
|
|
1310
|
-
"operate": {
|
|
1311
|
-
"operation": {
|
|
1312
|
-
"next_node_name": "Buy Insurance",
|
|
1313
|
-
"forward": "buy_insurance"
|
|
1314
|
-
},
|
|
1315
|
-
"op": "next"
|
|
1316
|
-
}
|
|
1317
|
-
},
|
|
1318
|
-
"env": {
|
|
1319
|
-
"account": "travel_provider",
|
|
1320
|
-
"network": "testnet",
|
|
1321
|
-
"no_cache": true
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
```
|
|
1326
|
-
|
|
1327
|
-
### 7.2 Progress to SPA
|
|
1328
|
-
|
|
1329
|
-
Move from "Buy Insurance" to "SPA" node.
|
|
1330
|
-
|
|
1331
|
-
**Prompt**: Operate progress to move to SPA node.
|
|
1332
|
-
|
|
1333
|
-
```json
|
|
1334
|
-
{
|
|
1335
|
-
"tool": "onchain_operations",
|
|
1336
|
-
"data": {
|
|
1337
|
-
"operation_type": "progress",
|
|
1338
|
-
"data": {
|
|
1339
|
-
"object": "alice_travel_progress",
|
|
1340
|
-
"operate": {
|
|
1341
|
-
"operation": {
|
|
1342
|
-
"next_node_name": "SPA",
|
|
1343
|
-
"forward": "go_spa"
|
|
1344
|
-
},
|
|
1345
|
-
"op": "next"
|
|
1346
|
-
}
|
|
1347
|
-
},
|
|
1348
|
-
"env": {
|
|
1349
|
-
"account": "travel_provider",
|
|
1350
|
-
"network": "testnet",
|
|
1351
|
-
"no_cache": true
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
```
|
|
1356
|
-
|
|
1357
|
-
### 7.3 Progress to Ice Scooting (with Weather Guard Submission)
|
|
1358
|
-
|
|
1359
|
-
Move from "SPA" to "Ice Scooting" node. This forward has a Guard (`weather_check_guard`) that verifies weather data exists in the repository for the activity date. You must submit the activity date timestamp (one of the sunny-day timestamps from Step 0.1) at runtime.
|
|
1360
|
-
|
|
1361
|
-
**Prompt**: Operate progress to move to Ice Scooting node with weather Guard submission.
|
|
1362
|
-
|
|
1363
|
-
```json
|
|
1364
|
-
{
|
|
1365
|
-
"tool": "onchain_operations",
|
|
1366
|
-
"data": {
|
|
1367
|
-
"operation_type": "progress",
|
|
1368
|
-
"data": {
|
|
1369
|
-
"object": "alice_travel_progress",
|
|
1370
|
-
"operate": {
|
|
1371
|
-
"operation": {
|
|
1372
|
-
"next_node_name": "Ice Scooting",
|
|
1373
|
-
"forward": "go_ice_scooting"
|
|
1374
|
-
},
|
|
1375
|
-
"op": "next"
|
|
1376
|
-
}
|
|
1377
|
-
},
|
|
1378
|
-
"submission": {
|
|
1379
|
-
"type": "submission",
|
|
1380
|
-
"guard": [
|
|
1381
|
-
{
|
|
1382
|
-
"object": "weather_check_guard",
|
|
1383
|
-
"impack": true
|
|
1384
|
-
}
|
|
1385
|
-
],
|
|
1386
|
-
"submission": [
|
|
1387
|
-
{
|
|
1388
|
-
"guard": "weather_check_guard",
|
|
1389
|
-
"submission": [
|
|
1390
|
-
{
|
|
1391
|
-
"identifier": 2,
|
|
1392
|
-
"b_submission": true,
|
|
1393
|
-
"value_type": "U64",
|
|
1394
|
-
"value": <ACTIVITY_DATE_TIMESTAMP>,
|
|
1395
|
-
"name": "Activity date timestamp"
|
|
1396
|
-
}
|
|
1397
|
-
]
|
|
1398
|
-
}
|
|
1399
|
-
]
|
|
1400
|
-
},
|
|
1401
|
-
"env": {
|
|
1402
|
-
"account": "travel_provider",
|
|
1403
|
-
"network": "testnet",
|
|
1404
|
-
"no_cache": true
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
```
|
|
1409
|
-
|
|
1410
|
-
> **Note**: Replace `<ACTIVITY_DATE_TIMESTAMP>` with one of the sunny-day timestamps from Step 0.1 (e.g., `1783555200000` for Day 1). The timestamp **must exactly match** the `id` used when adding weather data to the repository in Step 0.4 — Repository data is keyed by timestamp, and the Guard queries `repository.data has("Condition", convert_number_address(activity_date))`.
|
|
1411
|
-
>
|
|
1412
|
-
> **Key**: Only `identifier: 2` (the activity date) is submitted at runtime. Identifiers 0 (weather_repo) and 1 ("Condition") are fixed in the Guard table and do not need to be submitted. The `submission` field is at the **top level** of the input (alongside `data` and `env`), NOT inside `data`.
|
|
1413
|
-
|
|
1414
|
-
### 7.4 Complete Trip (with Guard Submission)
|
|
1415
|
-
|
|
1416
|
-
Move from "Ice Scooting" to "Complete" node. This forward has a Guard (`travel_complete_guard`) that requires the Order ID to be submitted for time-lock verification.
|
|
1417
|
-
|
|
1418
|
-
**Prompt**: Operate progress to complete the trip with Guard submission.
|
|
1419
|
-
|
|
1420
|
-
```json
|
|
1421
|
-
{
|
|
1422
|
-
"tool": "onchain_operations",
|
|
1423
|
-
"data": {
|
|
1424
|
-
"operation_type": "progress",
|
|
1425
|
-
"data": {
|
|
1426
|
-
"object": "alice_travel_progress",
|
|
1427
|
-
"operate": {
|
|
1428
|
-
"operation": {
|
|
1429
|
-
"next_node_name": "Complete",
|
|
1430
|
-
"forward": "complete_trip"
|
|
1431
|
-
},
|
|
1432
|
-
"op": "next"
|
|
1433
|
-
}
|
|
1434
|
-
},
|
|
1435
|
-
"submission": {
|
|
1436
|
-
"type": "submission",
|
|
1437
|
-
"guard": [
|
|
1438
|
-
{
|
|
1439
|
-
"object": "travel_complete_guard",
|
|
1440
|
-
"impack": true
|
|
1441
|
-
}
|
|
1442
|
-
],
|
|
1443
|
-
"submission": [
|
|
1444
|
-
{
|
|
1445
|
-
"guard": "travel_complete_guard",
|
|
1446
|
-
"submission": [
|
|
1447
|
-
{
|
|
1448
|
-
"identifier": 0,
|
|
1449
|
-
"b_submission": true,
|
|
1450
|
-
"value_type": "Address",
|
|
1451
|
-
"value": "<ORDER_OBJECT_ID>",
|
|
1452
|
-
"name": "Order ID"
|
|
1453
|
-
}
|
|
1454
|
-
]
|
|
1455
|
-
}
|
|
1456
|
-
]
|
|
1457
|
-
},
|
|
1458
|
-
"env": {
|
|
1459
|
-
"account": "travel_provider",
|
|
1460
|
-
"network": "testnet",
|
|
1461
|
-
"no_cache": true
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
```
|
|
1466
|
-
|
|
1467
|
-
> **Note**: Replace `<ORDER_OBJECT_ID>` with the actual Order object ID (a 64-hex-character string starting with `0x`). You can query the Progress object to find the `task` field which contains the Order ID.
|
|
1468
|
-
>
|
|
1469
|
-
> **Key**: The `submission` field is at the **top level** of the input (alongside `data` and `env`), NOT inside `data`. The `impack: true` means the Guard verification result affects the final outcome.
|
|
1470
|
-
|
|
1471
|
-
### 7.5 Cancel Trip (Alternative Path)
|
|
1472
|
-
|
|
1473
|
-
> **Note**: This is an **alternative** to Step 7.4. Once the trip is Completed, it cannot be Cancelled (and vice versa). Use this path only if cancelling from the "Ice Scooting" node.
|
|
1474
|
-
|
|
1475
|
-
**Prompt**: Operate progress to cancel the trip with Guard submission.
|
|
1476
|
-
|
|
1477
|
-
```json
|
|
1478
|
-
{
|
|
1479
|
-
"tool": "onchain_operations",
|
|
1480
|
-
"data": {
|
|
1481
|
-
"operation_type": "progress",
|
|
1482
|
-
"data": {
|
|
1483
|
-
"object": "alice_travel_progress",
|
|
1484
|
-
"operate": {
|
|
1485
|
-
"operation": {
|
|
1486
|
-
"next_node_name": "Cancel",
|
|
1487
|
-
"forward": "cancel_trip"
|
|
1488
|
-
},
|
|
1489
|
-
"op": "next"
|
|
1490
|
-
}
|
|
1491
|
-
},
|
|
1492
|
-
"submission": {
|
|
1493
|
-
"type": "submission",
|
|
1494
|
-
"guard": [
|
|
1495
|
-
{
|
|
1496
|
-
"object": "travel_cancel_guard",
|
|
1497
|
-
"impack": true
|
|
1498
|
-
}
|
|
1499
|
-
],
|
|
1500
|
-
"submission": [
|
|
1501
|
-
{
|
|
1502
|
-
"guard": "travel_cancel_guard",
|
|
1503
|
-
"submission": []
|
|
1504
|
-
}
|
|
1505
|
-
]
|
|
1506
|
-
},
|
|
1507
|
-
"env": {
|
|
1508
|
-
"account": "travel_provider",
|
|
1509
|
-
"network": "testnet",
|
|
1510
|
-
"no_cache": true
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
```
|
|
1515
|
-
|
|
1516
|
-
> **Note**: The `travel_cancel_guard` always passes (returns true), so no submission data is needed. The empty `submission: []` is sufficient.
|
|
1517
|
-
|
|
1518
|
-
**Progress Operation Structure**:
|
|
1519
|
-
|
|
1520
|
-
| Field | Type | Description |
|
|
1521
|
-
|-------|------|-------------|
|
|
1522
|
-
| `data.object` | string | Progress object name/ID |
|
|
1523
|
-
| `data.operate.operation.next_node_name` | string | Target node name to move to |
|
|
1524
|
-
| `data.operate.operation.forward` | string | Forward name defined in Machine |
|
|
1525
|
-
| `data.operate.op` | string | **Required.** Operation type: `"next"` (advance the forward), `"hold"`, `"unhold"`, or `"adminUnhold"` |
|
|
1526
|
-
| `submission` | object | Guard verification data (top-level, required when forward has Guard) |
|
|
1527
|
-
| `env.no_cache` | boolean | Set to `true` to avoid stale cache issues |
|
|
1528
|
-
|
|
1529
|
-
---
|
|
1530
|
-
|
|
1531
|
-
## Step 8: Execute Fund Allocation
|
|
1532
|
-
|
|
1533
|
-
After the Progress reaches a terminal state (Complete or Cancel), execute the fund allocation. This operation verifies the allocation Guard and distributes funds according to the `order_allocators` configured in Step 5.
|
|
1534
|
-
|
|
1535
|
-
**Anyone can call this operation** — the caller does not need to be the merchant or the customer. The Guard verification determines which allocator's sharing rules apply, and funds are distributed to the recipients defined in those rules.
|
|
1536
|
-
|
|
1537
|
-
### 8.1 Query the Order ID
|
|
1538
|
-
|
|
1539
|
-
The allocation Guard requires the Order ID as a submission (identifier: 0). Query the Progress object to find the `task` field, which contains the Order ID.
|
|
1540
|
-
|
|
1541
|
-
**Prompt**: Query the Progress object to get the Order ID.
|
|
1542
|
-
|
|
1543
|
-
```json
|
|
1544
|
-
{
|
|
1545
|
-
"tool": "query_toolkit",
|
|
1546
|
-
"data": {
|
|
1547
|
-
"query_type": "onchain_objects",
|
|
1548
|
-
"objects": ["alice_travel_progress"],
|
|
1549
|
-
"network": "testnet",
|
|
1550
|
-
"no_cache": true
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
```
|
|
1554
|
-
|
|
1555
|
-
> **Note**: Use `query_toolkit` (not `onchain_operations`) for read-only lookups — queries consume no gas and never mutate state. The response includes a `task` field containing the Order object ID. Copy this value for the allocation submission.
|
|
1556
|
-
|
|
1557
|
-
### 8.2 Execute Allocation (Merchant Victory Path)
|
|
1558
|
-
|
|
1559
|
-
When the Progress is "Complete", the `merchant_victory_guard` passes, and 100% of funds go to the Treasury (`travel_treasury`).
|
|
1560
|
-
|
|
1561
|
-
**Prompt**: Execute fund allocation with the merchant_victory_guard, submitting the Order ID.
|
|
1562
|
-
|
|
1563
|
-
```json
|
|
1564
|
-
{
|
|
1565
|
-
"tool": "onchain_operations",
|
|
1566
|
-
"data": {
|
|
1567
|
-
"operation_type": "allocation",
|
|
1568
|
-
"data": {
|
|
1569
|
-
"object": "alice_travel_allocation",
|
|
1570
|
-
"alloc_by_guard": "merchant_victory_guard"
|
|
1571
|
-
},
|
|
1572
|
-
"submission": {
|
|
1573
|
-
"type": "submission",
|
|
1574
|
-
"guard": [
|
|
1575
|
-
{
|
|
1576
|
-
"object": "merchant_victory_guard",
|
|
1577
|
-
"impack": true
|
|
1578
|
-
}
|
|
1579
|
-
],
|
|
1580
|
-
"submission": [
|
|
1581
|
-
{
|
|
1582
|
-
"guard": "merchant_victory_guard",
|
|
1583
|
-
"submission": [
|
|
1584
|
-
{
|
|
1585
|
-
"identifier": 0,
|
|
1586
|
-
"b_submission": true,
|
|
1587
|
-
"value_type": "Address",
|
|
1588
|
-
"value": "<ORDER_OBJECT_ID>",
|
|
1589
|
-
"name": "Order ID"
|
|
1590
|
-
}
|
|
1591
|
-
]
|
|
1592
|
-
}
|
|
1593
|
-
]
|
|
1594
|
-
},
|
|
1595
|
-
"env": {
|
|
1596
|
-
"account": "travel_provider",
|
|
1597
|
-
"network": "testnet",
|
|
1598
|
-
"no_cache": true
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
```
|
|
1603
|
-
|
|
1604
|
-
> **Note**: Replace `<ORDER_OBJECT_ID>` with the actual Order object ID from Step 8.1.
|
|
1605
|
-
>
|
|
1606
|
-
> **Two-phase submission**: If the first call returns a submission prompt (without executing), re-call with the `submission` field populated as shown above.
|
|
1607
|
-
|
|
1608
|
-
### 8.3 Execute Allocation (Refund Paths)
|
|
1609
|
-
|
|
1610
|
-
For refund scenarios (Cancel or SPA), use the corresponding Guard. The same submission structure applies — the Order ID is submitted to identifier 0.
|
|
1611
|
-
|
|
1612
|
-
**Cancel/Ice Scooting path** (80% Treasury, 20% Order (escrow, claimable by the order owner)):
|
|
1613
|
-
|
|
1614
|
-
```json
|
|
1615
|
-
{
|
|
1616
|
-
"tool": "onchain_operations",
|
|
1617
|
-
"data": {
|
|
1618
|
-
"operation_type": "allocation",
|
|
1619
|
-
"data": {
|
|
1620
|
-
"object": "alice_travel_allocation",
|
|
1621
|
-
"alloc_by_guard": "no_ice_scooting_guard"
|
|
1622
|
-
},
|
|
1623
|
-
"submission": {
|
|
1624
|
-
"type": "submission",
|
|
1625
|
-
"guard": [{"object": "no_ice_scooting_guard", "impack": true}],
|
|
1626
|
-
"submission": [{
|
|
1627
|
-
"guard": "no_ice_scooting_guard",
|
|
1628
|
-
"submission": [{"identifier": 0, "b_submission": true, "value_type": "Address", "value": "<ORDER_OBJECT_ID>", "name": "Order ID"}]
|
|
1629
|
-
}]
|
|
1630
|
-
},
|
|
1631
|
-
"env": {"account": "travel_provider", "network": "testnet", "no_cache": true, "confirmed": true}
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
```
|
|
1635
|
-
|
|
1636
|
-
**SPA path** (5% Treasury, 95% Order (escrow, claimable by the order owner)):
|
|
1637
|
-
|
|
1638
|
-
```json
|
|
1639
|
-
{
|
|
1640
|
-
"tool": "onchain_operations",
|
|
1641
|
-
"data": {
|
|
1642
|
-
"operation_type": "allocation",
|
|
1643
|
-
"data": {
|
|
1644
|
-
"object": "alice_travel_allocation",
|
|
1645
|
-
"alloc_by_guard": "no_spa_guard"
|
|
1646
|
-
},
|
|
1647
|
-
"submission": {
|
|
1648
|
-
"type": "submission",
|
|
1649
|
-
"guard": [{"object": "no_spa_guard", "impack": true}],
|
|
1650
|
-
"submission": [{
|
|
1651
|
-
"guard": "no_spa_guard",
|
|
1652
|
-
"submission": [{"identifier": 0, "b_submission": true, "value_type": "Address", "value": "<ORDER_OBJECT_ID>", "name": "Order ID"}]
|
|
1653
|
-
}]
|
|
1654
|
-
},
|
|
1655
|
-
"env": {"account": "travel_provider", "network": "testnet", "no_cache": true, "confirmed": true}
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
```
|
|
1659
|
-
|
|
1660
|
-
### 8.4 Verify Allocation Result
|
|
1661
|
-
|
|
1662
|
-
After allocation, query the Allocation and Payment objects to verify the fund distribution.
|
|
1663
|
-
|
|
1664
|
-
**Prompt**: Query the Allocation object to verify the balance and payment records.
|
|
1665
|
-
|
|
1666
|
-
```json
|
|
1667
|
-
{
|
|
1668
|
-
"tool": "query_toolkit",
|
|
1669
|
-
"data": {
|
|
1670
|
-
"query_type": "onchain_objects",
|
|
1671
|
-
"objects": ["alice_travel_allocation"],
|
|
1672
|
-
"network": "testnet",
|
|
1673
|
-
"no_cache": true
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
```
|
|
1677
|
-
|
|
1678
|
-
> **Expected result**: The Allocation `balance` should be 0 (all funds distributed), and the `payment` array should contain the Payment object ID(s) created by the allocation.
|
|
1679
|
-
|
|
1680
|
-
**Allocation Operation Structure**:
|
|
1681
|
-
|
|
1682
|
-
| Field | Type | Description |
|
|
1683
|
-
|-------|------|-------------|
|
|
1684
|
-
| `data.object` | string | Allocation object name/ID |
|
|
1685
|
-
| `data.alloc_by_guard` | string | Guard name/ID to verify (determines which allocator's rules apply) |
|
|
1686
|
-
| `submission` | object | Guard submission data (Order ID at identifier 0) |
|
|
1687
|
-
| `env.no_cache` | boolean | Set to `true` to avoid stale cache issues |
|
|
1688
|
-
|
|
1689
|
-
> **Important**: `alloc_by_guard` accepts Guard names (e.g., `"merchant_victory_guard"`) or Guard object IDs. The Guard must exist in the Allocation's `allocators` list. Only the first Guard that passes (first-Guard-wins) triggers fund distribution.
|
|
1690
|
-
|
|
1691
|
-
### 8.5 Claim Allocated Funds (Unwrap CoinWrapper)
|
|
1692
|
-
|
|
1693
|
-
Allocation does not deposit spendable coins directly. `alloc_by_guard` distributes **CoinWrapper objects** to each recipient address — these must be claimed/unwrapped before the funds are spendable:
|
|
1694
|
-
|
|
1695
|
-
- **Treasury share** (`Entity` → `travel_treasury`): the CoinWrapper is sent to the Treasury object address. The travel provider deposits it into the Treasury balance via the Treasury `receive` operation.
|
|
1696
|
-
- **Customer share** (`GuardIdentifier: 0` → the Order object address submitted at runtime, i.e. escrow): the CoinWrapper is sent to the Order object address. The order owner (Alice) claims it via the Order `receive` operation, which unwraps it and transfers the coins to her wallet.
|
|
1697
|
-
|
|
1698
|
-
**Prompt**: Alice claims the customer share escrowed to the Order (refund paths only).
|
|
1699
|
-
|
|
1700
|
-
```json
|
|
1701
|
-
{
|
|
1702
|
-
"tool": "onchain_operations",
|
|
1703
|
-
"data": {
|
|
1704
|
-
"operation_type": "order",
|
|
1705
|
-
"data": {
|
|
1706
|
-
"object": "alice_travel_order",
|
|
1707
|
-
"receive": "recently"
|
|
1708
|
-
},
|
|
1709
|
-
"env": {
|
|
1710
|
-
"account": "alice",
|
|
1711
|
-
"network": "testnet",
|
|
1712
|
-
"no_cache": true,
|
|
1713
|
-
"confirmed": true
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
```
|
|
1718
|
-
|
|
1719
|
-
**Prompt**: The travel provider deposits the Treasury's share into the Treasury balance.
|
|
1720
|
-
|
|
1721
|
-
```json
|
|
1722
|
-
{
|
|
1723
|
-
"tool": "onchain_operations",
|
|
1724
|
-
"data": {
|
|
1725
|
-
"operation_type": "treasury",
|
|
1726
|
-
"data": {
|
|
1727
|
-
"object": "travel_treasury",
|
|
1728
|
-
"receive": "recently"
|
|
1729
|
-
},
|
|
1730
|
-
"env": {
|
|
1731
|
-
"account": "travel_provider",
|
|
1732
|
-
"network": "testnet",
|
|
1733
|
-
"no_cache": true,
|
|
1734
|
-
"confirmed": true
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
```
|
|
1739
|
-
|
|
1740
|
-
> **Note**: `"receive": "recently"` auto-queries and claims all recently received CoinWrapper objects. For the Order, `receive` unwraps them and transfers the coins to the order owner (Alice); for the Treasury, `receive` deposits the unwrapped coins into the Treasury's balance. In the merchant-victory path (100% to Treasury) only the Treasury claim is needed; the Order claim applies only to refund paths (8.3) where the customer has a share.
|
|
1741
|
-
|
|
1742
|
-
---
|
|
1743
|
-
|
|
1744
|
-
## Best Practices
|
|
1745
|
-
|
|
1746
|
-
### 1. Guard Root Format
|
|
1747
|
-
|
|
1748
|
-
Guards use a direct GuardNode as the `root` field — no wrapper needed:
|
|
1749
|
-
```json
|
|
1750
|
-
"root": {
|
|
1751
|
-
"type": "logic_equal",
|
|
1752
|
-
"nodes": [...]
|
|
1753
|
-
}
|
|
1754
|
-
```
|
|
1755
|
-
|
|
1756
|
-
### 2. Machine Node Structure
|
|
1757
|
-
|
|
1758
|
-
Each node's `pairs` define how to **enter** that node. The `prev_node` specifies the source, and `forwards` are the operations to advance:
|
|
1759
|
-
```json
|
|
1760
|
-
{
|
|
1761
|
-
"name": "TargetNode",
|
|
1762
|
-
"pairs": [{
|
|
1763
|
-
"prev_node": "SourceNode",
|
|
1764
|
-
"threshold": 1,
|
|
1765
|
-
"forwards": [{"name": "forward_name", "weight": 1, "permissionIndex": 1000}]
|
|
1766
|
-
}]
|
|
1767
|
-
}
|
|
1768
|
-
```
|
|
1769
|
-
|
|
1770
|
-
- Use `prev_node` (not `prior_node`)
|
|
1771
|
-
- `threshold` is required for every pair
|
|
1772
|
-
- Consolidate forwards with the same `prev_node` into one pair
|
|
1773
|
-
|
|
1774
|
-
### 3. Machine Publish Before Service
|
|
1775
|
-
|
|
1776
|
-
The Machine must be published (`"publish": true`) before binding it to a Service. Unpublished Machines cannot be referenced by Services.
|
|
1777
|
-
|
|
1778
|
-
### 4. Service Creation in One Step
|
|
1779
|
-
|
|
1780
|
-
Create the Service with all configurations — including `pause: false` and `publish: true` — in a single operation:
|
|
1781
|
-
```json
|
|
1782
|
-
{
|
|
1783
|
-
"pause": false,
|
|
1784
|
-
"publish": true,
|
|
1785
|
-
"order_allocators": {...},
|
|
1786
|
-
"sales": {...},
|
|
1787
|
-
"machine": "...",
|
|
1788
|
-
"arbitrations": {...}
|
|
1789
|
-
}
|
|
1790
|
-
```
|
|
1791
|
-
|
|
1792
|
-
### 5. Guard Submission for Progress
|
|
1793
|
-
|
|
1794
|
-
When a forward has a Guard, the Progress operation requires a `submission` field at the **top level** (not inside `data`):
|
|
1795
|
-
```json
|
|
1796
|
-
{
|
|
1797
|
-
"tool": "onchain_operations",
|
|
1798
|
-
"data": {
|
|
1799
|
-
"operation_type": "progress",
|
|
1800
|
-
"data": {...},
|
|
1801
|
-
"submission": {
|
|
1802
|
-
"type": "submission",
|
|
1803
|
-
"guard": [{"object": "guard_name", "impack": true}],
|
|
1804
|
-
"submission": [{
|
|
1805
|
-
"guard": "guard_name",
|
|
1806
|
-
"submission": [{"identifier": 0, "b_submission": true, "value_type": "Address", "value": "0x...", "name": "Order ID"}]
|
|
1807
|
-
}]
|
|
1808
|
-
},
|
|
1809
|
-
"env": {"no_cache": true, ...}
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
```
|
|
1813
|
-
|
|
1814
|
-
### 6. Cache Management
|
|
1815
|
-
|
|
1816
|
-
Add `"no_cache": true` to the `env` field for all Progress operations and queries after mutations to avoid stale cache issues.
|
|
1817
|
-
|
|
1818
|
-
### 7. Weather Data Timestamps
|
|
1819
|
-
|
|
1820
|
-
Weather data in the Repository is keyed by timestamp (`id`). The `weather_check_guard` queries `repository.data has("Condition", convert_number_address(activity_date))` — the `activity_date` submitted at runtime in Step 7.3 **must exactly match** the `id` used when adding data in Step 0.4.
|
|
1821
|
-
|
|
1822
|
-
- Always align timestamps to UTC 00:00:00 (`Math.floor(now / DAY_MS) * DAY_MS`) so they are reproducible.
|
|
1823
|
-
- Re-run `calc-weather-timestamps.js` at test time — the values depend on the current date.
|
|
1824
|
-
- The Guard only checks data **existence**, not the condition value. A "rainy" day will still pass. To reject by condition value, use a Guard querying `repository.data` with a value comparison.
|
|
1825
|
-
|
|
1826
|
-
### 8. Order Placement
|
|
1827
|
-
|
|
1828
|
-
Customers place orders using the `order_new` field in the Service operation. This automatically creates Order, Progress, and Allocation objects:
|
|
1829
|
-
```json
|
|
1830
|
-
{
|
|
1831
|
-
"tool": "onchain_operations",
|
|
1832
|
-
"data": {
|
|
1833
|
-
"operation_type": "service",
|
|
1834
|
-
"data": {
|
|
1835
|
-
"object": "service_name",
|
|
1836
|
-
"order_new": {
|
|
1837
|
-
"buy": {
|
|
1838
|
-
"items": [{"name": "Product", "stock": "1", "wip_hash": ""}],
|
|
1839
|
-
"total_pay": {"balance": "price_amount"}
|
|
1840
|
-
},
|
|
1841
|
-
"namedNewOrder": {"name": "order_name", "replaceExistName": true},
|
|
1842
|
-
"namedNewProgress": {"name": "progress_name", "replaceExistName": true},
|
|
1843
|
-
"namedNewAllocation": {"name": "allocation_name", "replaceExistName": true}
|
|
1844
|
-
}
|
|
1845
|
-
},
|
|
1846
|
-
"env": {"account": "customer", "network": "testnet", "no_cache": true, "confirmed": true}
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
```
|