@5ive-tech/cli 1.0.15 → 1.0.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/templates/AGENTS.md +541 -83
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5ive-tech/cli",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "High-performance CLI for Five VM development with WebAssembly integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,69 +31,17 @@ Never rely on stale docs when behavior is high-stakes (deploy/execute/CPI encodi
31
31
  5. Execute and verify confirmed tx metadata (`meta.err == null`).
32
32
  6. Record signatures + compute units.
33
33
 
34
- ## 3.1) Strict Authoring Mode (Required For New Contracts)
35
-
36
- Use this profile unless you have direct proof in your current compiler version that a broader syntax feature is stable.
37
-
38
- 1. Prefer top-level accounts + top-level functions. Do not wrap programs in `script Name { ... }`.
39
- 2. Use account/signer/mut attributes in this order style:
40
- - `owner: pubkey @signer`
41
- - `state: Mint @mut`
42
- - `ata: TokenAccount @mut @init`
43
- 3. Prefer plain function declarations like:
44
- - `pub transfer(...) { ... }`
45
- - `pub mint_to(...) { ... }`
46
- 4. Any function intended to be called via `5ive execute` must be declared `pub`.
47
- 5. Keep logic to `require`, `if/else`, arithmetic, assignments, and simple returns.
48
- 6. Validate after every meaningful edit:
49
- - `5ive compile src/main.v -o build/main.five`
50
-
51
- Avoid these patterns by default (high-risk for invalid output in agent-generated code):
52
- 1. `instruction ...` function form
53
- 2. Attribute-first parameters like `@signer owner: pubkey`
54
- 3. Optional parameters/fields in signatures (`x?: T`) and nullish expressions (`??`)
55
- 4. `enum`/`event` heavy first-pass contracts
56
- 5. Event emission (`event` / `emit`) unless you have compile+runtime proof in your current toolchain
57
- 6. Unverified literals/helpers like `pubkey(0)` unless confirmed in working examples
58
- 7. Parser-only/experimental syntax from tokenizer docs without a compile-verified example
59
-
60
- Safe token-style baseline:
61
- ```v
62
- account Mint {
63
- authority: pubkey;
64
- supply: u64;
65
- decimals: u8;
66
- }
34
+ ### 3.1 Strict Authoring Rules
67
35
 
68
- account TokenAccount {
69
- owner_key: pubkey;
70
- mint: pubkey;
71
- bal: u64;
72
- }
73
-
74
- pub init_mint(
75
- state: Mint @mut,
76
- authority: pubkey @signer,
77
- decimals: u8
78
- ) {
79
- state.authority = authority;
80
- state.supply = 0;
81
- state.decimals = decimals;
82
- }
36
+ These rules are non-negotiable and prevent the most common compilation failures:
83
37
 
84
- pub transfer(
85
- source: TokenAccount @mut,
86
- destination: TokenAccount @mut,
87
- owner: pubkey @signer,
88
- amount: u64
89
- ) {
90
- require(source.owner_key == owner, "owner mismatch");
91
- require(source.mint == destination.mint, "mint mismatch");
92
- require(source.bal >= amount, "insufficient funds");
93
- source.bal = source.bal - amount;
94
- destination.bal = destination.bal + amount;
95
- }
96
- ```
38
+ 1. **All account fields must end with `;`** — missing semicolons cause parser failure.
39
+ 2. **Use `account @signer` for authorization** — not `pubkey @signer`. This preserves the `.key` accessor.
40
+ 3. **Use `.key` on `account`-typed parameters** to extract public keys for comparisons and assignments.
41
+ 4. **Use `-> ReturnType` for functions with return values** — e.g. `-> u64`, `-> pubkey`, `-> bool`.
42
+ 5. **`pubkey(0)` and `0` are valid** for zero-initializing pubkey fields (disabling authorities).
43
+ 6. **`string<N>` is production-safe** — use freely in accounts and function parameters.
44
+ 7. **All comparison operators work in `require()`** `==`, `!=`, `<`, `<=`, `>`, `>=`, `!`.
97
45
 
98
46
  ## 4) DSL Feature Inventory (Deep)
99
47
 
@@ -101,7 +49,7 @@ This section enumerates language features discovered from parser/compiler code a
101
49
 
102
50
  ### 4.1 Top-level declarations
103
51
  Observed and/or parsed:
104
- 1. `account Name { ... }`
52
+ 1. `account Name { ... }` — **all fields must be terminated with `;`**
105
53
  2. Global fields/variables (including `mut`)
106
54
  3. `init { ... }` block
107
55
  4. `constraints { ... }` block
@@ -111,6 +59,21 @@ Observed and/or parsed:
111
59
  8. `use` / `import` statements
112
60
  9. Legacy `script Name { ... }` wrapper (parser-supported)
113
61
 
62
+ ```v
63
+ // ✅ CORRECT — semicolons required
64
+ account Mint {
65
+ authority: pubkey;
66
+ supply: u64;
67
+ decimals: u8;
68
+ }
69
+
70
+ // ❌ WRONG — parser failure
71
+ account Mint {
72
+ authority: pubkey
73
+ supply: u64
74
+ }
75
+ ```
76
+
114
77
  ### 4.2 Function definition forms
115
78
  Parser accepts flexible forms:
116
79
  1. `pub add(...) -> ... { ... }`
@@ -141,11 +104,28 @@ Common attributes:
141
104
 
142
105
  Examples also show legacy bracket seed forms after `@init`.
143
106
 
107
+ **Attribute stacking order for account parameters (empirically verified):**
108
+
109
+ ```
110
+ Type @mut @init(payer=name, space=bytes) @signer
111
+ ```
112
+
113
+ Example:
114
+ ```v
115
+ pub init_mint(
116
+ mint_account: Mint @mut @init(payer=authority, space=256) @signer,
117
+ authority: account @mut @signer,
118
+ ...
119
+ )
120
+ ```
121
+
122
+ Order: (1) Type declaration → (2) `@mut` → (3) `@init(...)` → (4) `@signer`.
123
+
144
124
  ### 4.5 Types
145
125
  Supported/parsed type families:
146
126
  1. Primitive numeric/bool/pubkey/string types (`u8..u128`, `i8..i64`, `bool`, `pubkey`, `string`)
147
127
  2. `Account` type and account-typed params
148
- 3. Sized strings: `string<32>`
128
+ 3. **Sized strings: `string<N>`** — production-safe, use in accounts and function parameters
149
129
  4. Arrays:
150
130
  - Rust style: `[T; N]`
151
131
  - TypeScript-style sized: `T[N]`
@@ -158,20 +138,29 @@ Supported/parsed type families:
158
138
  - nested generics (`Option<Option<u64>>` etc.)
159
139
  8. Namespaced/custom types: `module::Type`
160
140
  9. Optional account fields in structs/accounts: `field?: Type`
141
+ 10. **`pubkey(0)` and integer `0`** — valid for zero-initialization of pubkey fields (interchangeable)
161
142
 
162
143
  ### 4.6 Statements
163
144
  Observed and parser-supported:
164
145
  1. `let` declarations (with `mut` and optional type annotation)
146
+ - Type inference works: `let is_owner = source.owner == authority.key;` infers `bool`
147
+ - Use `let` without explicit annotation for boolean and scalar expressions
165
148
  2. Assignment:
166
149
  - direct: `x = y`
167
150
  - compound: `+=`, `-=`, `*=`, `/=`, `<<=`, `>>=`, `&=`, `|=`, `^=`
168
151
  3. Field assignment: `obj.field = value`
169
- 4. Return statements (`return`, `return value`)
170
- 5. Guard/assertion: `require(condition)`
152
+ 4. Return statements (`return`, `return value`) — see §4.13 for return type syntax
153
+ 5. Guard/assertion: `require(condition)` — **all operators verified:**
154
+ - Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
155
+ - Boolean negation: `!expr`
156
+ - Logical: `&&`, `||`
157
+ - Example: `require(source.balance >= amount);`
158
+ - Example: `require(!account.is_frozen);`
171
159
  6. Conditionals:
172
160
  - `if (...) {}`
173
161
  - `else if (...) {}`
174
162
  - `else {}`
163
+ - Conditionals support nested `require()` statements and multiple assignments in both branches
175
164
  7. Pattern matching: `match expr { ... }`, with optional arm guards (`if ...`)
176
165
  8. Loops:
177
166
  - `while (...) { ... }`
@@ -268,24 +257,64 @@ From tokenizer/parser support:
268
257
 
269
258
  Repository tests also use comment-based param conventions (`// @test-params ...`) in many scripts.
270
259
 
271
- ### 4.12 Blockchain-oriented built-ins seen in examples
272
- Observed in scripts/templates:
260
+ ### 4.12 Blockchain-oriented built-ins
261
+ Core built-ins available in all contracts:
273
262
  1. `derive_pda(...)` (including bump-return and bump-specified variants)
274
263
  2. `get_clock()`
275
264
  3. `get_key(...)`
276
- 4. account key access: `authority.key`
265
+ 4. **Account key access: `param.key`** — **core pattern for all `account`-typed parameters.** Use `.key` to extract pubkeys for comparisons and assignments.
266
+
267
+ ```v
268
+ pub action(
269
+ state: MyAccount @mut,
270
+ caller: account @signer,
271
+ ...
272
+ ) {
273
+ require(state.authority == caller.key); // ownership check
274
+ state.last_actor = caller.key; // record who acted
275
+ }
276
+ ```
277
+
278
+ 5. **Authority revocation pattern:** assign `0` to any pubkey field to permanently disable it.
279
+ ```v
280
+ state.authority = 0; // revokes authority — irreversible
281
+ ```
282
+
283
+ ### 4.13 Return types and values
284
+ Functions can declare return types with `->` syntax:
285
+
286
+ ```v
287
+ pub get_value(state: MyAccount) -> u64 {
288
+ return state.amount;
289
+ }
290
+
291
+ pub initialize(
292
+ state: MyAccount @mut @init(payer=creator, space=256) @signer,
293
+ creator: account @mut @signer,
294
+ ...
295
+ ) -> pubkey {
296
+ state.authority = creator.key;
297
+ return state.key;
298
+ }
299
+ ```
277
300
 
278
- Treat built-ins as compiler/runtime coupled features; verify signatures in current examples before use.
301
+ Confirmed return types: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`..`i64`, `bool`, `pubkey`.
279
302
 
280
303
  ## 5) Feature Maturity Matrix (Agent Safety)
281
304
 
282
305
  ### 5.1 Generally production-oriented (widely used in templates)
283
- 1. Accounts, `@mut`, `@signer`, `@init`
284
- 2. `require`
285
- 3. Basic control flow (`if`, `while`)
306
+ 1. Accounts, `@mut`, `@signer`, `@init` (with attribute stacking)
307
+ 2. `require` with all comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`)
308
+ 3. Basic control flow (`if`, `else`, `while`) with nested logic
286
309
  4. Arithmetic/comparison/boolean expressions
287
310
  5. `.five` compile/deploy/execute path
288
311
  6. `interface` + explicit discriminator + explicit serializer CPI patterns
312
+ 7. Return type declarations (`-> Type`) with `return value;`
313
+ 8. `string<N>` fixed-size strings in accounts and parameters
314
+ 9. `account.key` extraction from `account`-typed parameters
315
+ 10. Authority disabling via `0` assignment to pubkey fields
316
+ 11. `let` with type inference for scalar/boolean expressions
317
+ 12. `pubkey(0)` zero-initialization
289
318
 
290
319
  ### 5.2 Available but validate per-version before critical use
291
320
  1. Match expressions with `Option`/`Result`
@@ -406,18 +435,205 @@ const program = FiveProgram.fromABI("<SCRIPT_ACCOUNT>", abi, {
406
435
  3. Surface signatures, CU metrics, and rich error states.
407
436
  4. Use LSP-backed editing where available to reduce DSL mistakes.
408
437
 
409
- ## 10) Pattern Mapping for Complex Contracts
438
+ ## 10) Contract Pattern Recipes
439
+
440
+ This section provides composable patterns for the most common contract archetypes. When building a novel contract, identify which patterns apply and combine them.
441
+
442
+ ### 10.1 Authority-Gated State (Vault, Treasury, Config)
443
+
444
+ Core pattern: one or more pubkey fields control who can mutate state. Used in almost every contract.
445
+
446
+ ```v
447
+ account Config {
448
+ authority: pubkey;
449
+ value: u64;
450
+ is_locked: bool;
451
+ }
452
+
453
+ pub update_value(
454
+ config: Config @mut,
455
+ authority: account @signer,
456
+ new_value: u64
457
+ ) {
458
+ require(config.authority == authority.key);
459
+ require(!config.is_locked);
460
+ config.value = new_value;
461
+ }
462
+ ```
463
+
464
+ **Key ingredients:** ownership check via `.key`, boolean guard with `!`, field mutation.
465
+
466
+ ### 10.2 Custody & Withdraw (Vault, Staking)
467
+
468
+ Core pattern: deposit into an account, enforce balance invariants on withdraw.
469
+
470
+ ```v
471
+ pub deposit(
472
+ vault: VaultAccount @mut,
473
+ depositor: account @signer,
474
+ amount: u64
475
+ ) {
476
+ require(amount > 0);
477
+ vault.balance = vault.balance + amount;
478
+ }
479
+
480
+ pub withdraw(
481
+ vault: VaultAccount @mut,
482
+ authority: account @signer,
483
+ amount: u64
484
+ ) {
485
+ require(vault.authority == authority.key);
486
+ require(vault.balance >= amount);
487
+ require(amount > 0);
488
+ vault.balance = vault.balance - amount;
489
+ }
490
+ ```
491
+
492
+ **Key ingredients:** `>= amount` balance guard, arithmetic on fields, `> 0` zero-amount prevention.
493
+
494
+ ### 10.3 Lifecycle State Machine (Escrow, Auction, Proposal)
495
+
496
+ Core pattern: a status field controls which operations are valid. Transitions are guarded.
497
+
498
+ ```v
499
+ account Escrow {
500
+ seller: pubkey;
501
+ buyer: pubkey;
502
+ amount: u64;
503
+ status: u8; // 0=open, 1=funded, 2=released, 3=cancelled
504
+ }
505
+
506
+ pub fund_escrow(
507
+ escrow: Escrow @mut,
508
+ buyer: account @signer,
509
+ amount: u64
510
+ ) {
511
+ require(escrow.buyer == buyer.key);
512
+ require(escrow.status == 0);
513
+ require(amount == escrow.amount);
514
+ escrow.status = 1;
515
+ }
516
+
517
+ pub release_escrow(
518
+ escrow: Escrow @mut,
519
+ buyer: account @signer
520
+ ) {
521
+ require(escrow.buyer == buyer.key);
522
+ require(escrow.status == 1);
523
+ escrow.status = 2;
524
+ }
525
+ ```
526
+
527
+ **Key ingredients:** integer status field with `==` checks for state transitions, dual-party authorization.
528
+
529
+ ### 10.4 Supply Accounting (Token, Mint, Points)
530
+
531
+ Core pattern: a central supply counter stays synchronized with distributed balances.
532
+
533
+ ```v
534
+ pub mint_to(
535
+ supply_state: SupplyAccount @mut,
536
+ destination: BalanceAccount @mut,
537
+ authority: account @signer,
538
+ amount: u64
539
+ ) {
540
+ require(supply_state.authority == authority.key);
541
+ require(amount > 0);
542
+ supply_state.total_supply = supply_state.total_supply + amount;
543
+ destination.balance = destination.balance + amount;
544
+ }
545
+
546
+ pub burn(
547
+ supply_state: SupplyAccount @mut,
548
+ source: BalanceAccount @mut,
549
+ owner: account @signer,
550
+ amount: u64
551
+ ) {
552
+ require(source.owner == owner.key);
553
+ require(source.balance >= amount);
554
+ source.balance = source.balance - amount;
555
+ supply_state.total_supply = supply_state.total_supply - amount;
556
+ }
557
+ ```
558
+
559
+ **Key ingredients:** paired increment/decrement across two accounts, conservation invariant.
560
+
561
+ ### 10.5 Delegation & Approval (Token, DAO, Proxy)
562
+
563
+ Core pattern: an owner grants limited permissions to a delegate.
564
+
565
+ ```v
566
+ pub approve(
567
+ state: DelegableAccount @mut,
568
+ owner: account @signer,
569
+ delegate: pubkey,
570
+ limit: u64
571
+ ) {
572
+ require(state.owner == owner.key);
573
+ state.delegate = delegate;
574
+ state.delegated_limit = limit;
575
+ }
576
+
577
+ pub revoke(
578
+ state: DelegableAccount @mut,
579
+ owner: account @signer
580
+ ) {
581
+ require(state.owner == owner.key);
582
+ state.delegate = 0;
583
+ state.delegated_limit = 0;
584
+ }
585
+ ```
586
+
587
+ **Key ingredients:** delegate pubkey field, limit tracking, zero-assignment to revoke.
588
+
589
+ ### 10.6 Conservation Math (AMM, Orderbook, Settlement)
410
590
 
411
- 1. Vault:
412
- - authority-gated custody, withdraw invariants
413
- 2. Escrow:
414
- - lifecycle transitions, dual-party authorization
415
- 3. Token/mint authority:
416
- - supply accounting, freeze/delegate controls
417
- 4. AMM/orderbook:
418
- - conservation math, deterministic settlement
419
- 5. Lending/perps/stablecoin:
420
- - collateral/liquidation thresholds, risk checks
591
+ Core pattern: total value across accounts must remain constant.
592
+
593
+ ```v
594
+ pub swap(
595
+ pool_a: PoolAccount @mut,
596
+ pool_b: PoolAccount @mut,
597
+ user_a: UserAccount @mut,
598
+ user_b: UserAccount @mut,
599
+ trader: account @signer,
600
+ amount_in: u64
601
+ ) {
602
+ require(user_a.owner == trader.key);
603
+ require(user_a.balance >= amount_in);
604
+ require(amount_in > 0);
605
+ let amount_out = (pool_b.reserve * amount_in) / (pool_a.reserve + amount_in);
606
+ require(amount_out > 0);
607
+ user_a.balance = user_a.balance - amount_in;
608
+ pool_a.reserve = pool_a.reserve + amount_in;
609
+ pool_b.reserve = pool_b.reserve - amount_out;
610
+ user_b.balance = user_b.balance + amount_out;
611
+ }
612
+ ```
613
+
614
+ **Key ingredients:** `let` with computed expression, multi-account mutation, balance checks on both sides.
615
+
616
+ ### 10.7 Threshold & Risk Checks (Lending, Collateral, Liquidation)
617
+
618
+ Core pattern: actions gated by ratio or threshold comparisons.
619
+
620
+ ```v
621
+ pub borrow(
622
+ position: LoanPosition @mut,
623
+ borrower: account @signer,
624
+ collateral_value: u64,
625
+ borrow_amount: u64
626
+ ) {
627
+ require(position.owner == borrower.key);
628
+ require(borrow_amount > 0);
629
+ // Enforce 150% collateral ratio: collateral * 100 >= total_debt * 150
630
+ let new_debt = position.debt + borrow_amount;
631
+ require(collateral_value * 100 >= new_debt * 150);
632
+ position.debt = new_debt;
633
+ }
634
+ ```
635
+
636
+ **Key ingredients:** `let` for intermediate computation, integer math for ratio checks, compound conditions.
421
637
 
422
638
  ## 11) Mainnet Safety Policy
423
639
 
@@ -464,3 +680,245 @@ Complete means:
464
680
  3. Avoid hidden defaults for deploy/CPI critical parameters.
465
681
  4. Keep changes auditable and reproducible.
466
682
  5. If uncertain, inspect compiler/CLI source directly.
683
+
684
+ ## 15) Agent One-Shot Contract Generation Procedure
685
+
686
+ Follow this procedure to produce correct 5IVE contracts on first compilation, regardless of contract type.
687
+
688
+ ### Step 1: Define account schemas
689
+ - Identify every distinct on-chain state object your contract needs.
690
+ - Each gets an `account Name { ... }` block with **all fields terminated by `;`**.
691
+ - Choose field types from: `pubkey`, `u8`–`u128`, `i8`–`i64`, `bool`, `string<N>`.
692
+ - Include an `authority: pubkey;` field on any account that needs access control.
693
+ - Include a `status: u8;` field for lifecycle/state-machine accounts.
694
+
695
+ ### Step 2: Define initializer functions
696
+ - For each account that users create at runtime, write an `init_*` function.
697
+ - The account parameter uses the full attribute stack: `Type @mut @init(payer=name, space=bytes) @signer`.
698
+ - The payer is `account @mut @signer`.
699
+ - Set every field to a known value (don't leave uninitialized fields).
700
+ - Return `-> pubkey` with `return account.key;` when callers need the address.
701
+
702
+ ### Step 3: Define action functions
703
+ - Every state-mutating function takes the relevant account(s) as `AccountType @mut`.
704
+ - Authorization: take an `account @signer` parameter, then `require(state.authority == signer.key);`.
705
+ - Guards: use `require()` with any comparison operator (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`).
706
+ - For balance operations: always check `require(source.balance >= amount);` before subtraction.
707
+ - For state machines: check `require(state.status == EXPECTED_STATUS);` before transition.
708
+ - Use `let` for intermediate computations (type inference handles it).
709
+
710
+ ### Step 4: Define read/query functions
711
+ - Use `-> ReturnType` syntax for functions that return values.
712
+ - `return state.field;` to return account data.
713
+
714
+ ### Step 5: Compile and verify
715
+ - Run `5ive build` or `5ive compile src/main.v -o build/main.five`.
716
+ - Fix any parser errors (most common: missing `;` in account fields).
717
+
718
+ ### Syntax quick-reference
719
+
720
+ | Pattern | Syntax |
721
+ |---|---|
722
+ | Account field | `name: type;` (semicolon required) |
723
+ | Init parameter | `acc: Type @mut @init(payer=p, space=N) @signer` |
724
+ | Signer parameter | `caller: account @signer` or `caller: account @mut @signer` |
725
+ | Ownership check | `require(state.authority == caller.key);` |
726
+ | Balance guard | `require(state.balance >= amount);` |
727
+ | Boolean guard | `require(!state.is_locked);` |
728
+ | Zero-amount guard | `require(amount > 0);` |
729
+ | Revoke authority | `state.authority = 0;` |
730
+ | Status transition | `state.status = 1;` |
731
+ | Local variable | `let x = expr;` |
732
+ | Return value | `pub fn(...) -> u64 { return state.value; }` |
733
+ | Fixed string field | `name: string<32>;` |
734
+ | Zero-init pubkey | `state.delegate = 0;` or `state.delegate = pubkey(0);` |
735
+
736
+ ## 16) Reference Implementations
737
+
738
+ Three verified, compilable patterns covering distinct contract archetypes. Use as canonical references.
739
+
740
+ ### 16.1 Token (Supply Accounting + Delegation + Freeze)
741
+
742
+ ```v
743
+ account Mint {
744
+ authority: pubkey;
745
+ freeze_authority: pubkey;
746
+ supply: u64;
747
+ decimals: u8;
748
+ name: string<32>;
749
+ symbol: string<32>;
750
+ }
751
+
752
+ account TokenAccount {
753
+ owner: pubkey;
754
+ mint: pubkey;
755
+ balance: u64;
756
+ is_frozen: bool;
757
+ delegate: pubkey;
758
+ delegated_amount: u64;
759
+ }
760
+
761
+ pub init_mint(
762
+ mint_account: Mint @mut @init(payer=authority, space=256) @signer,
763
+ authority: account @mut @signer,
764
+ freeze_authority: pubkey,
765
+ decimals: u8,
766
+ name: string<32>,
767
+ symbol: string<32>
768
+ ) -> pubkey {
769
+ require(decimals <= 20);
770
+ mint_account.authority = authority.key;
771
+ mint_account.freeze_authority = freeze_authority;
772
+ mint_account.supply = 0;
773
+ mint_account.decimals = decimals;
774
+ mint_account.name = name;
775
+ mint_account.symbol = symbol;
776
+ return mint_account.key;
777
+ }
778
+
779
+ pub transfer(
780
+ source: TokenAccount @mut,
781
+ destination: TokenAccount @mut,
782
+ owner: account @signer,
783
+ amount: u64
784
+ ) {
785
+ require(source.owner == owner.key);
786
+ require(source.balance >= amount);
787
+ require(source.mint == destination.mint);
788
+ require(!source.is_frozen);
789
+ require(!destination.is_frozen);
790
+ require(amount > 0);
791
+ source.balance = source.balance - amount;
792
+ destination.balance = destination.balance + amount;
793
+ }
794
+
795
+ pub approve(
796
+ source: TokenAccount @mut,
797
+ owner: account @signer,
798
+ delegate: pubkey,
799
+ amount: u64
800
+ ) {
801
+ require(source.owner == owner.key);
802
+ source.delegate = delegate;
803
+ source.delegated_amount = amount;
804
+ }
805
+ ```
806
+
807
+ **Patterns exercised:** `@init` stacking, supply accounting, freeze guards, delegation, `.key` extraction, `string<N>`, `-> pubkey` return.
808
+
809
+ ### 16.2 Vault (Custody + Authority Gating)
810
+
811
+ ```v
812
+ account Vault {
813
+ authority: pubkey;
814
+ balance: u64;
815
+ is_locked: bool;
816
+ }
817
+
818
+ pub init_vault(
819
+ vault: Vault @mut @init(payer=creator, space=128) @signer,
820
+ creator: account @mut @signer
821
+ ) -> pubkey {
822
+ vault.authority = creator.key;
823
+ vault.balance = 0;
824
+ vault.is_locked = false;
825
+ return vault.key;
826
+ }
827
+
828
+ pub deposit(
829
+ vault: Vault @mut,
830
+ depositor: account @signer,
831
+ amount: u64
832
+ ) {
833
+ require(!vault.is_locked);
834
+ require(amount > 0);
835
+ vault.balance = vault.balance + amount;
836
+ }
837
+
838
+ pub withdraw(
839
+ vault: Vault @mut,
840
+ authority: account @signer,
841
+ amount: u64
842
+ ) {
843
+ require(vault.authority == authority.key);
844
+ require(!vault.is_locked);
845
+ require(vault.balance >= amount);
846
+ require(amount > 0);
847
+ vault.balance = vault.balance - amount;
848
+ }
849
+
850
+ pub lock_vault(
851
+ vault: Vault @mut,
852
+ authority: account @signer
853
+ ) {
854
+ require(vault.authority == authority.key);
855
+ vault.is_locked = true;
856
+ }
857
+
858
+ pub transfer_authority(
859
+ vault: Vault @mut,
860
+ current_authority: account @signer,
861
+ new_authority: pubkey
862
+ ) {
863
+ require(vault.authority == current_authority.key);
864
+ vault.authority = new_authority;
865
+ }
866
+ ```
867
+
868
+ **Patterns exercised:** authority gating, boolean lock, balance guards, authority transfer, `@init` stacking.
869
+
870
+ ### 16.3 Escrow (Lifecycle State Machine + Dual-Party Auth)
871
+
872
+ ```v
873
+ account Escrow {
874
+ seller: pubkey;
875
+ buyer: pubkey;
876
+ amount: u64;
877
+ status: u8;
878
+ }
879
+
880
+ pub create_escrow(
881
+ escrow: Escrow @mut @init(payer=seller, space=128) @signer,
882
+ seller: account @mut @signer,
883
+ buyer: pubkey,
884
+ amount: u64
885
+ ) -> pubkey {
886
+ require(amount > 0);
887
+ escrow.seller = seller.key;
888
+ escrow.buyer = buyer;
889
+ escrow.amount = amount;
890
+ escrow.status = 0;
891
+ return escrow.key;
892
+ }
893
+
894
+ pub fund_escrow(
895
+ escrow: Escrow @mut,
896
+ buyer: account @signer,
897
+ amount: u64
898
+ ) {
899
+ require(escrow.buyer == buyer.key);
900
+ require(escrow.status == 0);
901
+ require(amount == escrow.amount);
902
+ escrow.status = 1;
903
+ }
904
+
905
+ pub release(
906
+ escrow: Escrow @mut,
907
+ buyer: account @signer
908
+ ) {
909
+ require(escrow.buyer == buyer.key);
910
+ require(escrow.status == 1);
911
+ escrow.status = 2;
912
+ }
913
+
914
+ pub cancel(
915
+ escrow: Escrow @mut,
916
+ seller: account @signer
917
+ ) {
918
+ require(escrow.seller == seller.key);
919
+ require(escrow.status == 0);
920
+ escrow.status = 3;
921
+ }
922
+ ```
923
+
924
+ **Patterns exercised:** integer status for state machine, dual-party authorization, lifecycle transitions, exact-amount matching.