@wowok/skills 1.1.13 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,15 @@ A complete e-commerce example demonstrating how to build an online store using W
4
4
 
5
5
  ---
6
6
 
7
+ ## ⚠️ Running Principle
8
+
9
+ > **Run the example in full every time (repeatable).** This example uses `replaceExistName: true` on all object creations — each run generates new objects with new addresses. If you skip build steps, operations may silently act on orphaned objects from previous runs, producing incorrect results. Old objects' configurations do not reflect the current document version.
10
+
11
+ - **Execution order**: Part 1 (Merchant Setup, Steps 1–7) → Part 2 (Customer Flow, Steps 1–9). Run all build steps in sequence before testing any customer flow. Do not skip steps — each depends on objects created by prior steps.
12
+ - **Prerequisites**: `myshop_merchant` with sufficient WOW for gas and order operations. All on-chain operations require `env.confirmed: true`.
13
+
14
+ ---
15
+
7
16
  ## Core Requirements & Features
8
17
 
9
18
  | Requirement | Description | Implementation |
@@ -443,7 +452,7 @@ Before creating the Service, create Guards that validate fund allocation conditi
443
452
 
444
453
  #### 5.1 Create Withdraw Guard (Merchant Withdrawal)
445
454
 
446
- Create a Guard that validates the order's Progress has reached the "Completed" node. This Guard uses `convert_witness: 100` (TypeOrderProgress) to query the Order's associated Progress object.
455
+ Create a Guard that validates the order's Progress has reached the "Completed" node. This Guard uses `convert_witness: "OrderProgress"` (TypeOrderProgress) to query the Order's associated Progress object.
447
456
 
448
457
  **Prompt**: Create a Guard named "myshop_withdraw_guard_v2" that verifies the order is completed before allowing merchant withdrawal.
449
458
 
@@ -453,41 +462,91 @@ Create a Guard that validates the order's Progress has reached the "Completed" n
453
462
  "data": {
454
463
  "namedNew": {
455
464
  "name": "myshop_withdraw_guard_v2",
456
- "tags": ["order", "completed", "withdraw"],
465
+ "tags": ["order", "completed", "withdraw", "signer-bound"],
457
466
  "onChain": false,
458
467
  "replaceExistName": true
459
468
  },
460
- "description": "Verify order progress is at Completed node for merchant withdrawal",
469
+ "description": "Verify order progress is at Completed node for merchant withdrawal. RISK ELIMINATION: Three-fold verification - (1) order at Completed node, (2) signer is myshop_merchant (prevents fund theft), (3) order belongs to myshop_service_v2 (prevents cross-service theft).",
461
470
  "table": [
462
471
  {
463
472
  "identifier": 0,
464
473
  "b_submission": true,
465
474
  "value_type": "Address",
466
- "name": "order_address"
475
+ "object_type": "Order",
476
+ "name": "order_address (Order object submitted at runtime)"
467
477
  },
468
478
  {
469
479
  "identifier": 1,
470
480
  "b_submission": false,
471
481
  "value_type": "String",
472
482
  "value": "Completed",
473
- "name": "completed_node"
483
+ "name": "Expected Completed node name (case-sensitive)"
484
+ },
485
+ {
486
+ "identifier": 2,
487
+ "b_submission": false,
488
+ "value_type": "Address",
489
+ "value": "myshop_merchant",
490
+ "name": "Authorized merchant address (prevents fund theft by unauthorized callers)"
491
+ },
492
+ {
493
+ "identifier": 3,
494
+ "b_submission": false,
495
+ "value_type": "Address",
496
+ "value": "myshop_service_v2",
497
+ "name": "Expected service address (prevents cross-service fund theft)"
474
498
  }
475
499
  ],
476
500
  "root": {
477
- "type": "logic_equal",
501
+ "type": "logic_and",
478
502
  "nodes": [
479
503
  {
480
- "type": "query",
481
- "query": 1253,
482
- "object": {
483
- "identifier": 0,
484
- "convert_witness": 100
485
- },
486
- "parameters": []
504
+ "type": "logic_equal",
505
+ "nodes": [
506
+ {
507
+ "type": "query",
508
+ "query": "progress.current",
509
+ "object": {
510
+ "identifier": 0,
511
+ "convert_witness": "OrderProgress"
512
+ },
513
+ "parameters": []
514
+ },
515
+ {
516
+ "type": "identifier",
517
+ "identifier": 1
518
+ }
519
+ ]
487
520
  },
488
521
  {
489
- "type": "identifier",
490
- "identifier": 1
522
+ "type": "logic_equal",
523
+ "nodes": [
524
+ {
525
+ "type": "context",
526
+ "context": "Signer"
527
+ },
528
+ {
529
+ "type": "identifier",
530
+ "identifier": 2
531
+ }
532
+ ]
533
+ },
534
+ {
535
+ "type": "logic_equal",
536
+ "nodes": [
537
+ {
538
+ "type": "query",
539
+ "query": "order.service",
540
+ "object": {
541
+ "identifier": 0
542
+ },
543
+ "parameters": []
544
+ },
545
+ {
546
+ "type": "identifier",
547
+ "identifier": 3
548
+ }
549
+ ]
491
550
  }
492
551
  ]
493
552
  }
@@ -500,14 +559,19 @@ Create a Guard that validates the order's Progress has reached the "Completed" n
500
559
  }
501
560
  ```
502
561
 
503
- **Guard Explanation:**
504
- - **Table Item 0**: Order address (submitted when activating allocation)
562
+ **Guard Explanation (Three-fold Verification):**
563
+ - **Table Item 0**: Order address (submitted at runtime, typed as Order object)
505
564
  - **Table Item 1**: Constant string "Completed" (the target node name)
506
- - **convert_witness: 100**: TypeOrderProgress - converts Order to its associated Progress
507
- - **Query "progress.current"**: Returns the current node name of the Progress
508
- - **logic_equal**: Verifies current node equals "Completed"
509
-
510
- > **Note**: The Guard `root` field directly specifies the GuardNode (e.g., `type: "logic_equal"`), not wrapped in a `type: "node"` object.
565
+ - **Table Item 2**: Constant address `myshop_merchant` (authorized merchant)
566
+ - **Table Item 3**: Constant address `myshop_service_v2` (this service's on-chain address)
567
+ - **Condition 1 — Order Completed**: `logic_equal[query("progress.current", witness="OrderProgress"), identifier[1]]` — queries the submitted Order's Progress (via witness "OrderProgress") and verifies the current node is "Completed"
568
+ - **Condition 2 — Signer is Merchant**: `logic_equal[context(Signer), identifier[2]]` — verifies the transaction caller is `myshop_merchant`, **preventing fund theft by unauthorized callers** (R-C3-01/R-C3-06)
569
+ - **Condition 3 Service Ownership**: `logic_equal[query("order.service"), identifier[3]]` queries the submitted Order's `service` field and verifies it equals `myshop_service_v2`, **preventing cross-service theft** where someone submits another service's Completed order (R-C3-05)
570
+ - **root**: `logic_and` of all three conditions — all must pass for allocation to proceed
571
+
572
+ > **Risk Elimination (R-C3-06)**: The allocator uses `"who": {"Signer": "signer"}` (funds go to the caller). This is safe ONLY because Condition 2 binds the Signer to `myshop_merchant`. Without this binding, anyone could submit any Completed order and steal 100% of funds. The three-fold verification ensures only the authorized merchant can trigger withdrawal.
573
+ >
574
+ > **Note**: The Guard `root` field directly specifies the GuardNode (e.g., `type: "logic_and"`), not wrapped in a `type: "node"` object.
511
575
 
512
576
  #### 5.2 Create Refund Guard (Customer Refund)
513
577
 
@@ -521,41 +585,88 @@ Create a Guard for customer refunds when order is cancelled.
521
585
  "data": {
522
586
  "namedNew": {
523
587
  "name": "myshop_refund_guard_v2",
524
- "tags": ["order", "cancelled", "refund"],
588
+ "tags": ["order", "cancelled", "refund", "signer-bound"],
525
589
  "onChain": false,
526
590
  "replaceExistName": true
527
591
  },
528
- "description": "Verify order progress is at Cancelled node for customer refund",
592
+ "description": "Verify order progress is at Cancelled node for customer refund. RISK ELIMINATION: Three-fold verification - (1) order at Cancelled node, (2) signer is order.owner (dynamic query, prevents fund theft - only order owner can trigger their own refund), (3) order belongs to myshop_service_v2 (prevents cross-service theft).",
529
593
  "table": [
530
594
  {
531
595
  "identifier": 0,
532
596
  "b_submission": true,
533
597
  "value_type": "Address",
534
- "name": "order_address"
598
+ "object_type": "Order",
599
+ "name": "order_address (Order object submitted at runtime)"
535
600
  },
536
601
  {
537
602
  "identifier": 1,
538
603
  "b_submission": false,
539
604
  "value_type": "String",
540
605
  "value": "Cancelled",
541
- "name": "cancelled_node"
606
+ "name": "Expected Cancelled node name (case-sensitive)"
607
+ },
608
+ {
609
+ "identifier": 2,
610
+ "b_submission": false,
611
+ "value_type": "Address",
612
+ "value": "myshop_service_v2",
613
+ "name": "Expected service address (prevents cross-service fund theft)"
542
614
  }
543
615
  ],
544
616
  "root": {
545
- "type": "logic_equal",
617
+ "type": "logic_and",
546
618
  "nodes": [
547
619
  {
548
- "type": "query",
549
- "query": 1253,
550
- "object": {
551
- "identifier": 0,
552
- "convert_witness": 100
553
- },
554
- "parameters": []
620
+ "type": "logic_equal",
621
+ "nodes": [
622
+ {
623
+ "type": "query",
624
+ "query": "progress.current",
625
+ "object": {
626
+ "identifier": 0,
627
+ "convert_witness": "OrderProgress"
628
+ },
629
+ "parameters": []
630
+ },
631
+ {
632
+ "type": "identifier",
633
+ "identifier": 1
634
+ }
635
+ ]
636
+ },
637
+ {
638
+ "type": "logic_equal",
639
+ "nodes": [
640
+ {
641
+ "type": "context",
642
+ "context": "Signer"
643
+ },
644
+ {
645
+ "type": "query",
646
+ "query": "order.owner",
647
+ "object": {
648
+ "identifier": 0
649
+ },
650
+ "parameters": []
651
+ }
652
+ ]
555
653
  },
556
654
  {
557
- "type": "identifier",
558
- "identifier": 1
655
+ "type": "logic_equal",
656
+ "nodes": [
657
+ {
658
+ "type": "query",
659
+ "query": "order.service",
660
+ "object": {
661
+ "identifier": 0
662
+ },
663
+ "parameters": []
664
+ },
665
+ {
666
+ "type": "identifier",
667
+ "identifier": 2
668
+ }
669
+ ]
559
670
  }
560
671
  ]
561
672
  }
@@ -568,6 +679,19 @@ Create a Guard for customer refunds when order is cancelled.
568
679
  }
569
680
  ```
570
681
 
682
+ **Guard Explanation (Three-fold Verification):**
683
+ - **Table Item 0**: Order address (submitted at runtime, typed as Order object)
684
+ - **Table Item 1**: Constant string "Cancelled" (the target node name)
685
+ - **Table Item 2**: Constant address `myshop_service_v2` (this service's on-chain address)
686
+ - **Condition 1 — Order Cancelled**: `logic_equal[query("progress.current", witness="OrderProgress"), identifier[1]]` — queries the submitted Order's Progress (via witness "OrderProgress") and verifies the current node is "Cancelled"
687
+ - **Condition 2 — Signer is Order Owner**: `logic_equal[context(Signer), query("order.owner")]` — verifies the transaction caller is the Order's owner (dynamic query, not a fixed address), **preventing fund theft by unauthorized callers** (R-C3-01/R-C3-06). Only the customer who placed the order can trigger their own refund.
688
+ - **Condition 3 — Service Ownership**: `logic_equal[query("order.service"), identifier[2]]` — queries the submitted Order's `service` field and verifies it equals `myshop_service_v2`, **preventing cross-service theft** (R-C3-05)
689
+ - **root**: `logic_and` of all three conditions — all must pass for refund allocation to proceed
690
+
691
+ > **Risk Elimination (R-C3-06)**: Unlike the withdraw Guard (which binds Signer to a fixed merchant address), the refund Guard binds Signer to `order.owner` via a **dynamic query** (query 1562). This is because refunds flow to the customer, and each order has a different customer. Only the order's rightful owner can trigger the refund — an attacker cannot submit another customer's Cancelled order.
692
+ >
693
+ > **Refund Recipient Design**: The allocator uses `"who": {"GuardIdentifier": 0}` (funds go to the Order object's address, not the caller's wallet). This creates an escrow pattern: the refund is held at the Order object's address, and the customer subsequently claims it via a separate withdraw operation. This ensures traceability and audit trail.
694
+
571
695
  ---
572
696
 
573
697
  ### Step 6: Create Service (Store)
@@ -588,9 +712,15 @@ The `order_allocators` configuration defines how order payments are distributed:
588
712
  | **Threshold** | Minimum amount to trigger allocation |
589
713
 
590
714
  **Recipient Types:**
591
- - `{ "Signer": "signer" }` - Transaction sender (merchant)
592
- - `{ "Entity": { "name_or_address": "..." } }` - Specific address or account name
593
- - `{ "GuardIdentifier": 0 }` - Address from Guard table
715
+ - `{ "Signer": "signer" }` - Transaction sender (caller). **⚠️ R-C3-06 Risk**: If the Guard does NOT bind the Signer to an authorized address, anyone who passes the Guard can steal 100% of funds. Safe ONLY when the Guard includes a `logic_equal[context(Signer), <authorized_address>]` check.
716
+ - `{ "Entity": { "name_or_address": "..." } }` - Specific address or account name (safest — funds go to a fixed address regardless of caller)
717
+ - `{ "GuardIdentifier": 0 }` - Address from Guard table (e.g., the submitted Order object's address)
718
+
719
+ > **R-C3-06 Risk Elimination — Guard + Sharing Coupling**: The `order_allocators` scene couples Guard verification (WHO can trigger) with sharing recipient (WHERE funds go). This example uses two risk-elimination strategies:
720
+ > - **Withdraw allocator** (`sharing.who = Signer`): Safe because `myshop_withdraw_guard_v2` binds `context(Signer)` to `myshop_merchant` (identifier 2). Only the merchant can pass the Guard, so funds correctly flow to the merchant.
721
+ > - **Refund allocator** (`sharing.who = GuardIdentifier 0`): Funds go to the Order object's address (escrow), not to the caller. The Guard additionally binds `context(Signer)` to `order.owner` (dynamic query 1562), ensuring only the order's rightful owner can trigger the refund.
722
+ >
723
+ > **Alternative approach**: Instead of Signer binding in the Guard, you can use `sharing.who = Entity` to send funds to a fixed Treasury or personal address. This is even more robust because funds are directed regardless of who passes the Guard. See the Insurance example for this pattern.
594
724
 
595
725
  > **Design Decision — Refund Recipient**: When using `{ "GuardIdentifier": 0 }` in the refund allocation, the refund is sent to the **Order object's on-chain address** (not the customer's wallet address). This is by design: the Order object acts as an escrow holding the refunded payment at its own address. The customer subsequently claims the refund from the Order object via a separate withdraw operation. This two-step design ensures the refund is traceable on-chain and tied to the specific order, providing better dispute resolution and audit trail.
596
726