@btc-vision/btc-runtime 1.10.8 → 1.10.11

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 (44) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +258 -137
  3. package/SECURITY.md +226 -0
  4. package/docs/README.md +614 -0
  5. package/docs/advanced/bitcoin-scripts.md +939 -0
  6. package/docs/advanced/cross-contract-calls.md +579 -0
  7. package/docs/advanced/plugins.md +1006 -0
  8. package/docs/advanced/quantum-resistance.md +660 -0
  9. package/docs/advanced/signature-verification.md +715 -0
  10. package/docs/api-reference/blockchain.md +729 -0
  11. package/docs/api-reference/events.md +642 -0
  12. package/docs/api-reference/op20.md +902 -0
  13. package/docs/api-reference/op721.md +819 -0
  14. package/docs/api-reference/safe-math.md +510 -0
  15. package/docs/api-reference/storage.md +840 -0
  16. package/docs/contracts/op-net-base.md +786 -0
  17. package/docs/contracts/op20-token.md +687 -0
  18. package/docs/contracts/op20s-signatures.md +614 -0
  19. package/docs/contracts/op721-nft.md +785 -0
  20. package/docs/contracts/reentrancy-guard.md +787 -0
  21. package/docs/core-concepts/blockchain-environment.md +724 -0
  22. package/docs/core-concepts/decorators.md +466 -0
  23. package/docs/core-concepts/events.md +652 -0
  24. package/docs/core-concepts/pointers.md +391 -0
  25. package/docs/core-concepts/security.md +473 -0
  26. package/docs/core-concepts/storage-system.md +969 -0
  27. package/docs/examples/basic-token.md +745 -0
  28. package/docs/examples/nft-with-reservations.md +1440 -0
  29. package/docs/examples/oracle-integration.md +1212 -0
  30. package/docs/examples/stablecoin.md +1180 -0
  31. package/docs/getting-started/first-contract.md +575 -0
  32. package/docs/getting-started/installation.md +384 -0
  33. package/docs/getting-started/project-structure.md +630 -0
  34. package/docs/storage/memory-maps.md +764 -0
  35. package/docs/storage/stored-arrays.md +778 -0
  36. package/docs/storage/stored-maps.md +758 -0
  37. package/docs/storage/stored-primitives.md +655 -0
  38. package/docs/types/address.md +773 -0
  39. package/docs/types/bytes-writer-reader.md +938 -0
  40. package/docs/types/calldata.md +744 -0
  41. package/docs/types/safe-math.md +446 -0
  42. package/package.json +52 -27
  43. package/runtime/memory/MapOfMap.ts +1 -0
  44. package/LICENSE.md +0 -21
@@ -0,0 +1,758 @@
1
+ # Stored Maps
2
+
3
+ Stored maps provide key-value storage on-chain, similar to Solidity's mappings. They support various key and value types with efficient storage access.
4
+
5
+ ## Overview
6
+
7
+ ```typescript
8
+ import {
9
+ StoredMapU256,
10
+ Blockchain,
11
+ } from '@btc-vision/btc-runtime/runtime';
12
+ import { u256 } from '@btc-vision/as-bignum/assembly';
13
+
14
+ // Allocate storage pointer
15
+ private dataPointer: u16 = Blockchain.nextPointer;
16
+
17
+ // Create stored map
18
+ private data: StoredMapU256;
19
+
20
+ constructor() {
21
+ super();
22
+ this.data = new StoredMapU256(this.dataPointer);
23
+ }
24
+
25
+ // Operations
26
+ this.data.set(key, value);
27
+ const value = this.data.get(key);
28
+ const exists = this.data.has(key);
29
+ ```
30
+
31
+ ## CRITICAL: Map Implementation Warning
32
+
33
+ > **DO NOT USE AssemblyScript's Built-in Map**
34
+ >
35
+ > When creating custom map implementations or extending map functionality, you **MUST** use the Map class from `@btc-vision/btc-runtime/runtime`, NOT the built-in AssemblyScript Map.
36
+ >
37
+ > **Why the AssemblyScript Map is broken for blockchain:**
38
+ > - NOT optimized for blockchain storage patterns
39
+ > - Does NOT handle Uint8Array buffers as keys correctly
40
+ > - Does NOT work properly with Address key comparisons
41
+ > - Will cause silent data corruption or key collisions
42
+ >
43
+ > **CORRECT:**
44
+ > ```typescript
45
+ > import { Map } from '@btc-vision/btc-runtime/runtime';
46
+ >
47
+ > export class MyCustomMap<V> extends Map<Address, V> {
48
+ > // Your implementation
49
+ > }
50
+ > ```
51
+ >
52
+ > **WRONG:**
53
+ > ```typescript
54
+ > // DO NOT DO THIS - will break!
55
+ > const map = new Map<Uint8Array, u256>(); // AssemblyScript Map
56
+ > ```
57
+ >
58
+ > The btc-runtime Map is specifically designed to:
59
+ > - Handle Address and Uint8Array key comparisons correctly
60
+ > - Optimize for blockchain storage access patterns
61
+ > - Support proper serialization for persistent storage
62
+ > - Prevent key collisions with custom equality logic
63
+
64
+ ## StoredMapU256
65
+
66
+ Basic key-value map with `u256` keys and values. Each key-value pair is stored at a unique storage slot computed via SHA256:
67
+
68
+ ```mermaid
69
+ ---
70
+ config:
71
+ theme: dark
72
+ ---
73
+ flowchart LR
74
+ subgraph instance["StoredMapU256 Instance"]
75
+ A["pointer: u16"]
76
+ B["Storage Operations"]
77
+ end
78
+
79
+ subgraph kvstore["Key-Value Storage"]
80
+ C["key1: u256"] --> D["value1: u256"]
81
+ E["key2: u256"] --> F["value2: u256"]
82
+ G["key3: u256"] --> H["value3: u256"]
83
+ end
84
+
85
+ subgraph keycalc["Storage Key Calculation (SHA256)"]
86
+ I["map.pointer"]
87
+ J["user key (u256)"]
88
+ K["SHA256(pointer + key)"]
89
+ L["32-byte storage key"]
90
+ end
91
+
92
+ A --> I
93
+ B --> K
94
+ J --> K
95
+ K --> L
96
+ L --> M[("Blockchain Storage")]
97
+
98
+ C -.stored at.- M
99
+ E -.stored at.- M
100
+ G -.stored at.- M
101
+ ```
102
+
103
+ ```typescript
104
+ private balancesPointer: u16 = Blockchain.nextPointer;
105
+ private balances: StoredMapU256;
106
+
107
+ constructor() {
108
+ super();
109
+ this.balances = new StoredMapU256(this.balancesPointer);
110
+ }
111
+
112
+ // Set value
113
+ this.balances.set(userId, balance);
114
+
115
+ // Get value (returns u256.Zero if not set)
116
+ const balance: u256 = this.balances.get(userId);
117
+
118
+ // Check existence (StoredMapU256 doesn't have has() - compare with zero)
119
+ const exists: bool = !this.balances.get(userId).isZero();
120
+
121
+ // Delete (set to zero)
122
+ this.balances.set(userId, u256.Zero);
123
+ ```
124
+
125
+ ### Storage Layout
126
+
127
+ ```
128
+ Map Pointer + SHA256(key) -> value
129
+
130
+ For key=5, pointer=3:
131
+ Storage key = SHA256(3 || 5)
132
+ ```
133
+
134
+ ## Using Address Keys
135
+
136
+ For address-keyed mappings, convert address to `u256`:
137
+
138
+ ```typescript
139
+ // Address to u256 conversion
140
+ private addressToKey(addr: Address): u256 {
141
+ const bytes = addr.toBytes();
142
+ return u256.fromBytes(bytes);
143
+ }
144
+
145
+ // Get balance for address
146
+ public getBalance(addr: Address): u256 {
147
+ const key = this.addressToKey(addr);
148
+ return this.balances.get(key);
149
+ }
150
+
151
+ // Set balance for address
152
+ public setBalance(addr: Address, amount: u256): void {
153
+ const key = this.addressToKey(addr);
154
+ this.balances.set(key, amount);
155
+ }
156
+ ```
157
+
158
+ ## Nested Maps
159
+
160
+ For `mapping(key1 => mapping(key2 => value))`:
161
+
162
+ ```typescript
163
+ private allowancesPointer: u16 = Blockchain.nextPointer;
164
+ private allowances: StoredMapU256;
165
+
166
+ constructor() {
167
+ super();
168
+ this.allowances = new StoredMapU256(this.allowancesPointer);
169
+ }
170
+
171
+ // Create composite key
172
+ private allowanceKey(owner: Address, spender: Address): u256 {
173
+ const ownerBytes = owner.toBytes();
174
+ const spenderBytes = spender.toBytes();
175
+
176
+ // Combine and hash
177
+ const combined = new Uint8Array(64);
178
+ combined.set(ownerBytes, 0);
179
+ combined.set(spenderBytes, 32);
180
+
181
+ return u256.fromBytes(Blockchain.sha256(combined));
182
+ }
183
+
184
+ // Get allowance
185
+ public getAllowance(owner: Address, spender: Address): u256 {
186
+ const key = this.allowanceKey(owner, spender);
187
+ return this.allowances.get(key);
188
+ }
189
+
190
+ // Set allowance
191
+ public setAllowance(owner: Address, spender: Address, amount: u256): void {
192
+ const key = this.allowanceKey(owner, spender);
193
+ this.allowances.set(key, amount);
194
+ }
195
+ ```
196
+
197
+ ## MapOfMap
198
+
199
+ For complex nested mappings, use `MapOfMap`. This provides a two-level structure:
200
+
201
+ ```mermaid
202
+ ---
203
+ config:
204
+ theme: dark
205
+ ---
206
+ flowchart LR
207
+ A["MapOfMap instance<br/>pointer: u16"] --> B["owner1"]
208
+ A --> C["owner2"]
209
+ A --> D["owner3"]
210
+ B --> E1["Nested<u256>"]
211
+ C --> E2["Nested<u256>"]
212
+ D --> E3["Nested<u256>"]
213
+ E1 --> F1["spender1 -> u256"]
214
+ E1 --> F2["spender2 -> u256"]
215
+ E2 --> F3["spender1 -> u256"]
216
+ E3 --> F4["spender3 -> u256"]
217
+ ```
218
+
219
+ ```typescript
220
+ import { MapOfMap, Nested } from '@btc-vision/btc-runtime/runtime';
221
+
222
+ // mapping(address => mapping(address => uint256))
223
+ private allowancesPointer: u16 = Blockchain.nextPointer;
224
+ private allowances: MapOfMap<u256>;
225
+
226
+ constructor() {
227
+ super();
228
+ this.allowances = new MapOfMap<u256>(this.allowancesPointer);
229
+ }
230
+
231
+ // Get nested value - two-step process
232
+ public getAllowance(owner: Address, spender: Address): u256 {
233
+ const ownerMap = this.allowances.get(owner); // Returns Nested<u256>
234
+ return ownerMap.get(spender); // Returns u256
235
+ }
236
+
237
+ // Set nested value - get, modify, commit back
238
+ public setAllowance(owner: Address, spender: Address, amount: u256): void {
239
+ const ownerMap = this.allowances.get(owner); // Get the nested map
240
+ ownerMap.set(spender, amount); // Modify it
241
+ this.allowances.set(owner, ownerMap); // Commit back
242
+ }
243
+ ```
244
+
245
+ ### MapOfMap Get/Set Pattern
246
+
247
+ > **Important:** `MapOfMap.get(key)` returns a `Nested<T>` object, not the final value. You must call `.get()` on the nested object to retrieve the actual value. Similarly, when setting values, you must retrieve the nested map, modify it, then commit it back to the parent map.
248
+
249
+ ```mermaid
250
+ ---
251
+ config:
252
+ theme: dark
253
+ ---
254
+ flowchart LR
255
+ A["allowances.get(owner)"] --> B["Returns Nested<u256>"]
256
+ B --> C["nested.get(spender)"]
257
+ C --> D["Returns u256 value"]
258
+ E["allowances.get(owner)"] --> F["nested.set(spender, amount)"]
259
+ F --> G["allowances.set(owner, nested)"]
260
+ G --> H["Commit to storage"]
261
+ ```
262
+
263
+ ## Solidity vs OPNet Comparison
264
+
265
+ ### Quick Reference Table
266
+
267
+ | Solidity Mapping Type | OPNet Equivalent | Notes |
268
+ |-----------------------|------------------|-------|
269
+ | `mapping(uint256 => uint256)` | `StoredMapU256` | u256 keys and values |
270
+ | `mapping(address => uint256)` | `AddressMemoryMap` | Recommended for address keys |
271
+ | `mapping(address => uint256)` | `StoredMapU256` with `addressToKey()` | Alternative approach |
272
+ | `mapping(K => mapping(K2 => V))` | `MapOfMap<V>` | Two-level nesting |
273
+ | `mapping(K => mapping(K2 => V))` | `StoredMapU256` with composite key | Hash-based approach |
274
+
275
+ ### Operations Comparison
276
+
277
+ | Operation | Solidity | OPNet (StoredMapU256) |
278
+ |-----------|----------|----------------------|
279
+ | Declare | `mapping(uint256 => uint256) data;` | `private data: StoredMapU256;` |
280
+ | Initialize | Automatic | `this.data = new StoredMapU256(this.dataPointer);` |
281
+ | Read value | `data[key]` | `data.get(key)` |
282
+ | Write value | `data[key] = value;` | `data.set(key, value)` |
283
+ | Check exists | `data[key] != 0` | `!data.get(key).isZero()` |
284
+ | Delete entry | `delete data[key];` | `data.set(key, u256.Zero)` |
285
+ | Default value | `0` | `u256.Zero` |
286
+
287
+ ### Nested Mapping Comparison
288
+
289
+ | Operation | Solidity | OPNet (MapOfMap) |
290
+ |-----------|----------|------------------|
291
+ | Declare | `mapping(address => mapping(address => uint256)) allowances;` | `private allowances: MapOfMap<u256>;` |
292
+ | Read nested | `allowances[owner][spender]` | `allowances.get(owner).get(spender)` |
293
+ | Write nested | `allowances[owner][spender] = amount;` | `const m = allowances.get(owner); m.set(spender, amount); allowances.set(owner, m);` |
294
+
295
+ ### Address Key Patterns
296
+
297
+ | Solidity Pattern | OPNet Equivalent |
298
+ |------------------|------------------|
299
+ | `mapping(address => uint256) balances;` | `private balances: AddressMemoryMap;` (preferred) |
300
+ | `balances[msg.sender]` | `balances.get(Blockchain.tx.sender)` |
301
+ | `balances[addr] = x;` | `balances.set(addr, x)` |
302
+ | `balances[addr] += amount;` | `balances.set(addr, SafeMath.add(balances.get(addr), amount))` |
303
+
304
+ ### Common Use Cases
305
+
306
+ | Use Case | Solidity | OPNet |
307
+ |----------|----------|-------|
308
+ | Token balances | `mapping(address => uint256) balances;` | `AddressMemoryMap` |
309
+ | Approvals | `mapping(address => mapping(address => uint256))` | `MapOfMap<u256>` |
310
+ | Nonces | `mapping(address => uint256) nonces;` | `AddressMemoryMap` or `StoredMapU256` |
311
+ | Roles/permissions | `mapping(bytes32 => mapping(address => bool))` | `MapOfMap<u256>` with role hash |
312
+ | Token metadata | `mapping(uint256 => string)` | `StoredMapU256` with encoded strings |
313
+ | Checkpoints | `mapping(address => mapping(uint256 => uint256))` | `MapOfMap<u256>` or composite keys |
314
+
315
+ ### Full Example Comparison
316
+
317
+ ```solidity
318
+ // Solidity
319
+ contract Token {
320
+ mapping(address => uint256) public balances;
321
+ mapping(address => mapping(address => uint256)) public allowances;
322
+
323
+ function transfer(address to, uint256 amount) external {
324
+ require(balances[msg.sender] >= amount);
325
+ balances[msg.sender] -= amount;
326
+ balances[to] += amount;
327
+ }
328
+
329
+ function approve(address spender, uint256 amount) external {
330
+ allowances[msg.sender][spender] = amount;
331
+ }
332
+ }
333
+ ```
334
+
335
+ ```typescript
336
+ // OPNet
337
+ @final
338
+ export class Token extends OP_NET {
339
+ private balancesPointer: u16 = Blockchain.nextPointer;
340
+ private allowancesPointer: u16 = Blockchain.nextPointer;
341
+
342
+ private balances: StoredMapU256;
343
+ private allowances: MapOfMap<u256>;
344
+
345
+ constructor() {
346
+ super();
347
+ this.balances = new StoredMapU256(this.balancesPointer);
348
+ this.allowances = new MapOfMap<u256>(this.allowancesPointer);
349
+ }
350
+
351
+ private addressKey(addr: Address): u256 {
352
+ return u256.fromBytes(addr.toBytes());
353
+ }
354
+
355
+ public transfer(calldata: Calldata): BytesWriter {
356
+ const to = calldata.readAddress();
357
+ const amount = calldata.readU256();
358
+ const sender = Blockchain.tx.sender;
359
+
360
+ const senderKey = this.addressKey(sender);
361
+ const toKey = this.addressKey(to);
362
+
363
+ const senderBalance = this.balances.get(senderKey);
364
+ if (senderBalance < amount) {
365
+ throw new Revert('Insufficient balance');
366
+ }
367
+
368
+ this.balances.set(senderKey, SafeMath.sub(senderBalance, amount));
369
+ this.balances.set(toKey, SafeMath.add(this.balances.get(toKey), amount));
370
+
371
+ return new BytesWriter(0);
372
+ }
373
+
374
+ public approve(calldata: Calldata): BytesWriter {
375
+ const spender = calldata.readAddress();
376
+ const amount = calldata.readU256();
377
+ const sender = Blockchain.tx.sender;
378
+
379
+ // Correct two-step MapOfMap pattern
380
+ const senderMap = this.allowances.get(sender);
381
+ senderMap.set(spender, amount);
382
+ this.allowances.set(sender, senderMap);
383
+
384
+ return new BytesWriter(0);
385
+ }
386
+ }
387
+ ```
388
+
389
+ ## Side-by-Side Code Examples
390
+
391
+ ### Simple Key-Value Store
392
+
393
+ **Solidity:**
394
+ ```solidity
395
+ contract KeyValueStore {
396
+ mapping(uint256 => uint256) public data;
397
+
398
+ function set(uint256 key, uint256 value) external {
399
+ data[key] = value;
400
+ }
401
+
402
+ function get(uint256 key) external view returns (uint256) {
403
+ return data[key];
404
+ }
405
+
406
+ function remove(uint256 key) external {
407
+ delete data[key];
408
+ }
409
+
410
+ function increment(uint256 key) external {
411
+ data[key]++;
412
+ }
413
+ }
414
+ ```
415
+
416
+ **OPNet:**
417
+ ```typescript
418
+ @final
419
+ export class KeyValueStore extends OP_NET {
420
+ private dataPointer: u16 = Blockchain.nextPointer;
421
+ private data: StoredMapU256;
422
+
423
+ constructor() {
424
+ super();
425
+ this.data = new StoredMapU256(this.dataPointer);
426
+ }
427
+
428
+ public set(calldata: Calldata): BytesWriter {
429
+ const key = calldata.readU256();
430
+ const value = calldata.readU256();
431
+ this.data.set(key, value);
432
+ return new BytesWriter(0);
433
+ }
434
+
435
+ public get(calldata: Calldata): BytesWriter {
436
+ const key = calldata.readU256();
437
+ const writer = new BytesWriter(32);
438
+ writer.writeU256(this.data.get(key));
439
+ return writer;
440
+ }
441
+
442
+ public remove(calldata: Calldata): BytesWriter {
443
+ const key = calldata.readU256();
444
+ this.data.set(key, u256.Zero);
445
+ return new BytesWriter(0);
446
+ }
447
+
448
+ public increment(calldata: Calldata): BytesWriter {
449
+ const key = calldata.readU256();
450
+ this.data.set(key, SafeMath.add(this.data.get(key), u256.One));
451
+ return new BytesWriter(0);
452
+ }
453
+ }
454
+ ```
455
+
456
+ ### Approval System with Nested Mapping
457
+
458
+ **Solidity:**
459
+ ```solidity
460
+ contract ApprovalSystem {
461
+ mapping(address => mapping(address => uint256)) public allowances;
462
+
463
+ function approve(address spender, uint256 amount) external {
464
+ allowances[msg.sender][spender] = amount;
465
+ }
466
+
467
+ function allowance(address owner, address spender) external view returns (uint256) {
468
+ return allowances[owner][spender];
469
+ }
470
+
471
+ function increaseAllowance(address spender, uint256 addedValue) external {
472
+ allowances[msg.sender][spender] += addedValue;
473
+ }
474
+
475
+ function decreaseAllowance(address spender, uint256 subtractedValue) external {
476
+ uint256 currentAllowance = allowances[msg.sender][spender];
477
+ require(currentAllowance >= subtractedValue, "Decreased below zero");
478
+ allowances[msg.sender][spender] = currentAllowance - subtractedValue;
479
+ }
480
+
481
+ function spend(address owner, uint256 amount) external {
482
+ uint256 currentAllowance = allowances[owner][msg.sender];
483
+ require(currentAllowance >= amount, "Insufficient allowance");
484
+ allowances[owner][msg.sender] = currentAllowance - amount;
485
+ }
486
+ }
487
+ ```
488
+
489
+ **OPNet:**
490
+ ```typescript
491
+ @final
492
+ export class ApprovalSystem extends OP_NET {
493
+ private allowancesPointer: u16 = Blockchain.nextPointer;
494
+ private allowances: MapOfMap<u256>;
495
+
496
+ constructor() {
497
+ super();
498
+ this.allowances = new MapOfMap<u256>(this.allowancesPointer);
499
+ }
500
+
501
+ public approve(calldata: Calldata): BytesWriter {
502
+ const spender = calldata.readAddress();
503
+ const amount = calldata.readU256();
504
+ const sender = Blockchain.tx.sender;
505
+
506
+ const senderAllowances = this.allowances.get(sender);
507
+ senderAllowances.set(spender, amount);
508
+ this.allowances.set(sender, senderAllowances);
509
+
510
+ return new BytesWriter(0);
511
+ }
512
+
513
+ public allowance(calldata: Calldata): BytesWriter {
514
+ const owner = calldata.readAddress();
515
+ const spender = calldata.readAddress();
516
+
517
+ const ownerAllowances = this.allowances.get(owner);
518
+ const writer = new BytesWriter(32);
519
+ writer.writeU256(ownerAllowances.get(spender));
520
+ return writer;
521
+ }
522
+
523
+ public increaseAllowance(calldata: Calldata): BytesWriter {
524
+ const spender = calldata.readAddress();
525
+ const addedValue = calldata.readU256();
526
+ const sender = Blockchain.tx.sender;
527
+
528
+ const senderAllowances = this.allowances.get(sender);
529
+ const current = senderAllowances.get(spender);
530
+ senderAllowances.set(spender, SafeMath.add(current, addedValue));
531
+ this.allowances.set(sender, senderAllowances);
532
+
533
+ return new BytesWriter(0);
534
+ }
535
+
536
+ public decreaseAllowance(calldata: Calldata): BytesWriter {
537
+ const spender = calldata.readAddress();
538
+ const subtractedValue = calldata.readU256();
539
+ const sender = Blockchain.tx.sender;
540
+
541
+ const senderAllowances = this.allowances.get(sender);
542
+ const currentAllowance = senderAllowances.get(spender);
543
+
544
+ if (currentAllowance < subtractedValue) {
545
+ throw new Revert('Decreased below zero');
546
+ }
547
+
548
+ senderAllowances.set(spender, SafeMath.sub(currentAllowance, subtractedValue));
549
+ this.allowances.set(sender, senderAllowances);
550
+
551
+ return new BytesWriter(0);
552
+ }
553
+
554
+ public spend(calldata: Calldata): BytesWriter {
555
+ const owner = calldata.readAddress();
556
+ const amount = calldata.readU256();
557
+ const sender = Blockchain.tx.sender;
558
+
559
+ const ownerAllowances = this.allowances.get(owner);
560
+ const currentAllowance = ownerAllowances.get(sender);
561
+
562
+ if (currentAllowance < amount) {
563
+ throw new Revert('Insufficient allowance');
564
+ }
565
+
566
+ ownerAllowances.set(sender, SafeMath.sub(currentAllowance, amount));
567
+ this.allowances.set(owner, ownerAllowances);
568
+
569
+ return new BytesWriter(0);
570
+ }
571
+ }
572
+ ```
573
+
574
+ ## Common Patterns
575
+
576
+ ### Counter/Nonce Tracking
577
+
578
+ ```typescript
579
+ private noncesPointer: u16 = Blockchain.nextPointer;
580
+ private nonces: StoredMapU256;
581
+
582
+ constructor() {
583
+ super();
584
+ this.nonces = new StoredMapU256(this.noncesPointer);
585
+ }
586
+
587
+ public getNonce(addr: Address): u256 {
588
+ return this.nonces.get(this.addressKey(addr));
589
+ }
590
+
591
+ public incrementNonce(addr: Address): u256 {
592
+ const key = this.addressKey(addr);
593
+ const current = this.nonces.get(key);
594
+ const next = SafeMath.add(current, u256.One);
595
+ this.nonces.set(key, next);
596
+ return current; // Return old nonce
597
+ }
598
+
599
+ private addressKey(addr: Address): u256 {
600
+ return u256.fromBytes(addr.toBytes());
601
+ }
602
+ ```
603
+
604
+ ### Role Management
605
+
606
+ ```typescript
607
+ private rolesPointer: u16 = Blockchain.nextPointer;
608
+ private roles: StoredMapU256;
609
+
610
+ constructor() {
611
+ super();
612
+ this.roles = new StoredMapU256(this.rolesPointer);
613
+ }
614
+
615
+ private readonly ADMIN_ROLE: u256 = u256.One;
616
+ private readonly MINTER_ROLE: u256 = u256.fromU64(2);
617
+
618
+ public hasRole(addr: Address, role: u256): bool {
619
+ const key = this.roleKey(addr, role);
620
+ return !this.roles.get(key).isZero();
621
+ }
622
+
623
+ public grantRole(calldata: Calldata): BytesWriter {
624
+ this.onlyDeployer(Blockchain.tx.sender);
625
+
626
+ const addr = calldata.readAddress();
627
+ const role = calldata.readU256();
628
+
629
+ const key = this.roleKey(addr, role);
630
+ this.roles.set(key, u256.One);
631
+
632
+ return new BytesWriter(0);
633
+ }
634
+
635
+ private roleKey(addr: Address, role: u256): u256 {
636
+ // Combine address and role into unique key
637
+ const bytes = new Uint8Array(64);
638
+ bytes.set(addr.toBytes(), 0);
639
+ bytes.set(role.toBytes(), 32);
640
+ return u256.fromBytes(Blockchain.sha256(bytes));
641
+ }
642
+ ```
643
+
644
+ ### Token Metadata Storage
645
+
646
+ ```typescript
647
+ // Store arbitrary metadata per token ID
648
+ private metadataPointer: u16 = Blockchain.nextPointer;
649
+ private metadata: StoredMapU256;
650
+
651
+ constructor() {
652
+ super();
653
+ this.metadata = new StoredMapU256(this.metadataPointer);
654
+ }
655
+
656
+ // Each token can have multiple metadata fields
657
+ // Use composite keys: tokenId + fieldId
658
+
659
+ private readonly FIELD_NAME: u256 = u256.One;
660
+ private readonly FIELD_LEVEL: u256 = u256.fromU64(2);
661
+ private readonly FIELD_RARITY: u256 = u256.fromU64(3);
662
+
663
+ public getMetadata(tokenId: u256, field: u256): u256 {
664
+ const key = this.metadataKey(tokenId, field);
665
+ return this.metadata.get(key);
666
+ }
667
+
668
+ public setMetadata(tokenId: u256, field: u256, value: u256): void {
669
+ const key = this.metadataKey(tokenId, field);
670
+ this.metadata.set(key, value);
671
+ }
672
+
673
+ private metadataKey(tokenId: u256, field: u256): u256 {
674
+ const bytes = new Uint8Array(64);
675
+ bytes.set(tokenId.toBytes(), 0);
676
+ bytes.set(field.toBytes(), 32);
677
+ return u256.fromBytes(Blockchain.sha256(bytes));
678
+ }
679
+ ```
680
+
681
+ ### Snapshot/Checkpoint Pattern
682
+
683
+ ```typescript
684
+ // Store values at specific block numbers
685
+ private checkpointsPointer: u16 = Blockchain.nextPointer;
686
+ private checkpoints: StoredMapU256;
687
+
688
+ constructor() {
689
+ super();
690
+ this.checkpoints = new StoredMapU256(this.checkpointsPointer);
691
+ }
692
+
693
+ public checkpoint(addr: Address, value: u256): void {
694
+ const blockNumber = Blockchain.block.number;
695
+ const key = this.checkpointKey(addr, blockNumber);
696
+ this.checkpoints.set(key, value);
697
+ }
698
+
699
+ public getCheckpoint(addr: Address, blockNumber: u64): u256 {
700
+ const key = this.checkpointKey(addr, blockNumber);
701
+ return this.checkpoints.get(key);
702
+ }
703
+
704
+ private checkpointKey(addr: Address, blockNumber: u64): u256 {
705
+ const bytes = new Uint8Array(40);
706
+ bytes.set(addr.toBytes(), 0);
707
+ // Encode block number in remaining bytes
708
+ const blockBytes = new Uint8Array(8);
709
+ // ... encode blockNumber
710
+ bytes.set(blockBytes, 32);
711
+ return u256.fromBytes(Blockchain.sha256(bytes));
712
+ }
713
+ ```
714
+
715
+ ## Best Practices
716
+
717
+ ### 1. Use Consistent Key Functions
718
+
719
+ ```typescript
720
+ // Define key functions once
721
+ private addressKey(addr: Address): u256 {
722
+ return u256.fromBytes(addr.toBytes());
723
+ }
724
+
725
+ // Use consistently throughout contract
726
+ const key = this.addressKey(user);
727
+ ```
728
+
729
+ ### 2. Handle Default Values
730
+
731
+ ```typescript
732
+ // Map returns u256.Zero for unset keys
733
+ const balance = this.balances.get(key);
734
+
735
+ // Check if actually set vs zero balance
736
+ // Option 1: Use separate "exists" tracking
737
+ // Option 2: Use non-zero sentinel for "set"
738
+ // Option 3: Accept that zero and unset are equivalent
739
+ ```
740
+
741
+ ### 3. Document Key Structures
742
+
743
+ ```typescript
744
+ /**
745
+ * Storage layout:
746
+ * - balances: address -> u256
747
+ * Key: SHA256(balancesPointer || SHA256(address))
748
+ *
749
+ * - allowances: (owner, spender) -> u256
750
+ * Key: SHA256(allowancesPointer || SHA256(owner || spender))
751
+ */
752
+ ```
753
+
754
+ ---
755
+
756
+ **Navigation:**
757
+ - Previous: [Stored Arrays](./stored-arrays.md)
758
+ - Next: [Memory Maps](./memory-maps.md)