@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,764 @@
1
+ # Memory Maps
2
+
3
+ Memory maps provide a convenient interface for address-keyed storage with automatic type handling. They're the recommended way to implement Solidity-style `mapping(address => T)` patterns.
4
+
5
+ ## Overview
6
+
7
+ ```typescript
8
+ import {
9
+ AddressMemoryMap,
10
+ Blockchain,
11
+ Address,
12
+ } from '@btc-vision/btc-runtime/runtime';
13
+ import { u256 } from '@btc-vision/as-bignum/assembly';
14
+
15
+ // Allocate storage pointer
16
+ private balancesPointer: u16 = Blockchain.nextPointer;
17
+
18
+ // Create memory map
19
+ private balances: AddressMemoryMap;
20
+
21
+ constructor() {
22
+ super();
23
+ this.balances = new AddressMemoryMap(this.balancesPointer);
24
+ }
25
+
26
+ // Usage
27
+ const balance = this.balances.get(userAddress);
28
+ this.balances.set(userAddress, newBalance);
29
+ ```
30
+
31
+ ## AddressMemoryMap
32
+
33
+ The primary memory map type for address-keyed storage. It stores and returns u256 values directly.
34
+
35
+ ```typescript
36
+ class AddressMemoryMap
37
+ ```
38
+
39
+ ### Constructor Pattern
40
+
41
+ ```typescript
42
+ private balancesPointer: u16 = Blockchain.nextPointer;
43
+ private balances: AddressMemoryMap;
44
+
45
+ constructor() {
46
+ super();
47
+ this.balances = new AddressMemoryMap(this.balancesPointer);
48
+ }
49
+ ```
50
+
51
+ ### Methods
52
+
53
+ ```typescript
54
+ // Get value for address
55
+ public get(key: Address): u256
56
+
57
+ // Set value for address (returns this for chaining)
58
+ public set(key: Address, value: u256): this
59
+
60
+ // Get raw bytes
61
+ public getAsUint8Array(key: Address): Uint8Array
62
+
63
+ // Set raw bytes
64
+ public setAsUint8Array(key: Address, value: Uint8Array): this
65
+
66
+ // Check if key has non-default value
67
+ public has(key: Address): bool
68
+
69
+ // Delete (set to default, returns true if key existed)
70
+ public delete(key: Address): bool
71
+ ```
72
+
73
+ ## Storage Flow
74
+
75
+ When you access an AddressMemoryMap, the address is converted to a storage key via SHA256:
76
+
77
+ ```mermaid
78
+ ---
79
+ config:
80
+ theme: dark
81
+ ---
82
+ flowchart LR
83
+ subgraph map["AddressMemoryMap Instance"]
84
+ A["pointer: u16<br/>Storage base pointer"]
85
+ B["Internal Map<br/>Address -> u256"]
86
+ end
87
+
88
+ subgraph addrops["Address Key Operations"]
89
+ C["User Address"]
90
+ D["Address.toBytes()"]
91
+ E["32-byte key"]
92
+ end
93
+
94
+ subgraph keygen["Storage Key Generation"]
95
+ F["encodePointer()"]
96
+ G["pointer + address bytes"]
97
+ H["SHA256 hash"]
98
+ I["32-byte storage key"]
99
+ end
100
+
101
+ C --> D
102
+ D --> E
103
+ A --> F
104
+ E --> F
105
+ F --> G
106
+ G --> H
107
+ H --> I
108
+ I --> J[("Blockchain Storage")]
109
+ ```
110
+
111
+ ### Address to Storage Key
112
+
113
+ The complete flow from address to storage access:
114
+
115
+ ```mermaid
116
+ flowchart LR
117
+ subgraph input["Input"]
118
+ A["Address<br/>0x1234...abcd"]
119
+ end
120
+
121
+ subgraph getaddr["AddressMemoryMap.get(address)"]
122
+ B["toBytes()"]
123
+ C["32-byte Uint8Array"]
124
+ D["encodePointer()<br/>pointer + addressBytes"]
125
+ end
126
+
127
+ subgraph storage["Storage Access"]
128
+ E["32-byte storage key"]
129
+ F["Blockchain.getStorageAt()"]
130
+ G["Raw bytes from storage"]
131
+ H["u256.fromUint8ArrayBE()"]
132
+ end
133
+
134
+ subgraph output["Output"]
135
+ I["u256 value<br/>or u256.Zero if not set"]
136
+ end
137
+
138
+ A --> B
139
+ B --> C
140
+ C --> D
141
+ D --> E
142
+ E --> F
143
+ F --> G
144
+ G --> H
145
+ H --> I
146
+ ```
147
+
148
+ ## Solidity vs OPNet Comparison
149
+
150
+ ### Quick Reference Table
151
+
152
+ | Solidity | OPNet AddressMemoryMap |
153
+ |----------|------------------------|
154
+ | `mapping(address => uint256)` | `AddressMemoryMap` |
155
+ | `balances[addr]` | `balances.get(addr)` |
156
+ | `balances[addr] = val` | `balances.set(addr, val)` |
157
+ | Default value: `0` | Default value: `u256.Zero` |
158
+ | Implicit initialization | Explicit constructor initialization |
159
+ | No existence check | `balances.has(addr)` available |
160
+ | `delete balances[addr]` | `balances.delete(addr)` |
161
+
162
+ ### Operations Comparison
163
+
164
+ | Operation | Solidity | OPNet |
165
+ |-----------|----------|-------|
166
+ | Declare | `mapping(address => uint256) public balances;` | `private balances: AddressMemoryMap;` |
167
+ | Initialize | Automatic | `this.balances = new AddressMemoryMap(this.balancesPointer);` |
168
+ | Read | `balances[addr]` | `balances.get(addr)` |
169
+ | Write | `balances[addr] = amount;` | `balances.set(addr, amount)` |
170
+ | Add to value | `balances[addr] += amount;` | `balances.set(addr, SafeMath.add(balances.get(addr), amount))` |
171
+ | Subtract | `balances[addr] -= amount;` | `balances.set(addr, SafeMath.sub(balances.get(addr), amount))` |
172
+ | Check non-zero | `balances[addr] > 0` | `!balances.get(addr).isZero()` |
173
+ | Delete/reset | `delete balances[addr];` | `balances.delete(addr)` or `balances.set(addr, u256.Zero)` |
174
+ | Check exists | N/A (always 0 default) | `balances.has(addr)` |
175
+
176
+ ### Common Patterns
177
+
178
+ | Pattern | Solidity | OPNet |
179
+ |---------|----------|-------|
180
+ | Transfer balance | `balances[from] -= amt; balances[to] += amt;` | `balances.set(from, SafeMath.sub(balances.get(from), amt)); balances.set(to, SafeMath.add(balances.get(to), amt));` |
181
+ | Check sufficient | `require(balances[addr] >= amount);` | `if (balances.get(addr) < amount) throw new Revert("Insufficient");` |
182
+ | Mint tokens | `balances[to] += amount;` | `balances.set(to, SafeMath.add(balances.get(to), amount));` |
183
+ | Burn tokens | `balances[from] -= amount;` | `balances.set(from, SafeMath.sub(balances.get(from), amount));` |
184
+ | Zero balance check | `balances[addr] == 0` | `balances.get(addr).isZero()` |
185
+ | Get sender balance | `balances[msg.sender]` | `balances.get(Blockchain.tx.sender)` |
186
+
187
+ ### Key Differences from Solidity
188
+
189
+ | Aspect | Solidity | OPNet |
190
+ |--------|----------|-------|
191
+ | Key type | `address` (20 bytes) | `Address` (32 bytes) |
192
+ | Value type | Any | `u256` only |
193
+ | Storage slot | `keccak256(key . slot)` | `SHA256(pointer + address)` |
194
+ | Reentrancy safe | Developer responsibility | Developer responsibility |
195
+ | Arithmetic | Native operators | `SafeMath` required |
196
+
197
+ ### ERC-20 Style Comparison
198
+
199
+ | ERC-20 Function | Solidity | OPNet |
200
+ |-----------------|----------|-------|
201
+ | `balanceOf(address)` | `return balances[owner];` | `return this.balances.get(owner);` |
202
+ | `transfer(to, amount)` | `balances[msg.sender] -= amount; balances[to] += amount;` | `this.balances.set(sender, SafeMath.sub(...)); this.balances.set(to, SafeMath.add(...));` |
203
+ | `approve(spender, amount)` | `allowances[msg.sender][spender] = amount;` | Use `MapOfMap<u256>` for nested mapping |
204
+
205
+ ### Full Comparison Example
206
+
207
+ ```solidity
208
+ // Solidity
209
+ contract Token {
210
+ mapping(address => uint256) public balances;
211
+
212
+ function transfer(address to, uint256 amount) external {
213
+ require(balances[msg.sender] >= amount, "Insufficient");
214
+ balances[msg.sender] -= amount;
215
+ balances[to] += amount;
216
+ }
217
+ }
218
+ ```
219
+
220
+ ```typescript
221
+ // OPNet
222
+ @final
223
+ export class Token extends OP_NET {
224
+ private balancesPointer: u16 = Blockchain.nextPointer;
225
+ private balances: AddressMemoryMap;
226
+
227
+ constructor() {
228
+ super();
229
+ this.balances = new AddressMemoryMap(this.balancesPointer);
230
+ }
231
+
232
+ public transfer(calldata: Calldata): BytesWriter {
233
+ const to = calldata.readAddress();
234
+ const amount = calldata.readU256();
235
+ const sender = Blockchain.tx.sender;
236
+
237
+ const senderBalance = this.balances.get(sender);
238
+ if (senderBalance < amount) {
239
+ throw new Revert('Insufficient balance');
240
+ }
241
+
242
+ this.balances.set(sender, SafeMath.sub(senderBalance, amount));
243
+ this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
244
+
245
+ return new BytesWriter(0);
246
+ }
247
+ }
248
+ ```
249
+
250
+ ## Side-by-Side Code Examples
251
+
252
+ ### Basic Token Balance Tracking
253
+
254
+ **Solidity:**
255
+ ```solidity
256
+ contract TokenBalances {
257
+ mapping(address => uint256) public balances;
258
+ uint256 public totalSupply;
259
+
260
+ function mint(address to, uint256 amount) external {
261
+ balances[to] += amount;
262
+ totalSupply += amount;
263
+ }
264
+
265
+ function burn(address from, uint256 amount) external {
266
+ require(balances[from] >= amount, "Insufficient balance");
267
+ balances[from] -= amount;
268
+ totalSupply -= amount;
269
+ }
270
+
271
+ function transfer(address from, address to, uint256 amount) external {
272
+ require(balances[from] >= amount, "Insufficient balance");
273
+ balances[from] -= amount;
274
+ balances[to] += amount;
275
+ }
276
+
277
+ function balanceOf(address account) external view returns (uint256) {
278
+ return balances[account];
279
+ }
280
+ }
281
+ ```
282
+
283
+ **OPNet:**
284
+ ```typescript
285
+ @final
286
+ export class TokenBalances extends OP_NET {
287
+ private balancesPointer: u16 = Blockchain.nextPointer;
288
+ private totalSupplyPointer: u16 = Blockchain.nextPointer;
289
+
290
+ private balances: AddressMemoryMap;
291
+ private _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPointer, EMPTY_POINTER);
292
+
293
+ constructor() {
294
+ super();
295
+ this.balances = new AddressMemoryMap(this.balancesPointer);
296
+ }
297
+
298
+ public mint(calldata: Calldata): BytesWriter {
299
+ const to = calldata.readAddress();
300
+ const amount = calldata.readU256();
301
+
302
+ this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
303
+ this._totalSupply.value = SafeMath.add(this._totalSupply.value, amount);
304
+
305
+ return new BytesWriter(0);
306
+ }
307
+
308
+ public burn(calldata: Calldata): BytesWriter {
309
+ const from = calldata.readAddress();
310
+ const amount = calldata.readU256();
311
+
312
+ const balance = this.balances.get(from);
313
+ if (balance < amount) {
314
+ throw new Revert('Insufficient balance');
315
+ }
316
+
317
+ this.balances.set(from, SafeMath.sub(balance, amount));
318
+ this._totalSupply.value = SafeMath.sub(this._totalSupply.value, amount);
319
+
320
+ return new BytesWriter(0);
321
+ }
322
+
323
+ public transfer(calldata: Calldata): BytesWriter {
324
+ const from = calldata.readAddress();
325
+ const to = calldata.readAddress();
326
+ const amount = calldata.readU256();
327
+
328
+ const fromBalance = this.balances.get(from);
329
+ if (fromBalance < amount) {
330
+ throw new Revert('Insufficient balance');
331
+ }
332
+
333
+ this.balances.set(from, SafeMath.sub(fromBalance, amount));
334
+ this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
335
+
336
+ return new BytesWriter(0);
337
+ }
338
+
339
+ public balanceOf(calldata: Calldata): BytesWriter {
340
+ const account = calldata.readAddress();
341
+ const writer = new BytesWriter(32);
342
+ writer.writeU256(this.balances.get(account));
343
+ return writer;
344
+ }
345
+
346
+ public totalSupply(_calldata: Calldata): BytesWriter {
347
+ const writer = new BytesWriter(32);
348
+ writer.writeU256(this._totalSupply.value);
349
+ return writer;
350
+ }
351
+ }
352
+ ```
353
+
354
+ ### Staking Contract
355
+
356
+ **Solidity:**
357
+ ```solidity
358
+ contract Staking {
359
+ mapping(address => uint256) public stakedAmount;
360
+ mapping(address => uint256) public stakedTimestamp;
361
+ mapping(address => uint256) public rewards;
362
+
363
+ function stake(uint256 amount) external {
364
+ stakedAmount[msg.sender] += amount;
365
+ stakedTimestamp[msg.sender] = block.timestamp;
366
+ }
367
+
368
+ function unstake(uint256 amount) external {
369
+ require(stakedAmount[msg.sender] >= amount, "Not enough staked");
370
+ stakedAmount[msg.sender] -= amount;
371
+ }
372
+
373
+ function claimRewards() external {
374
+ uint256 reward = calculateReward(msg.sender);
375
+ rewards[msg.sender] = 0;
376
+ // Transfer reward...
377
+ }
378
+
379
+ function calculateReward(address user) public view returns (uint256) {
380
+ uint256 duration = block.timestamp - stakedTimestamp[user];
381
+ return stakedAmount[user] * duration / 365 days;
382
+ }
383
+
384
+ function getStakeInfo(address user) external view returns (uint256, uint256, uint256) {
385
+ return (stakedAmount[user], stakedTimestamp[user], rewards[user]);
386
+ }
387
+ }
388
+ ```
389
+
390
+ **OPNet:**
391
+ ```typescript
392
+ @final
393
+ export class Staking extends OP_NET {
394
+ private stakedAmountPointer: u16 = Blockchain.nextPointer;
395
+ private stakedTimestampPointer: u16 = Blockchain.nextPointer;
396
+ private rewardsPointer: u16 = Blockchain.nextPointer;
397
+
398
+ private stakedAmount: AddressMemoryMap;
399
+ private stakedTimestamp: AddressMemoryMap;
400
+ private rewards: AddressMemoryMap;
401
+
402
+ constructor() {
403
+ super();
404
+ this.stakedAmount = new AddressMemoryMap(this.stakedAmountPointer);
405
+ this.stakedTimestamp = new AddressMemoryMap(this.stakedTimestampPointer);
406
+ this.rewards = new AddressMemoryMap(this.rewardsPointer);
407
+ }
408
+
409
+ public stake(calldata: Calldata): BytesWriter {
410
+ const amount = calldata.readU256();
411
+ const sender = Blockchain.tx.sender;
412
+
413
+ this.stakedAmount.set(sender, SafeMath.add(this.stakedAmount.get(sender), amount));
414
+ this.stakedTimestamp.set(sender, u256.fromU64(Blockchain.block.medianTime));
415
+
416
+ return new BytesWriter(0);
417
+ }
418
+
419
+ public unstake(calldata: Calldata): BytesWriter {
420
+ const amount = calldata.readU256();
421
+ const sender = Blockchain.tx.sender;
422
+
423
+ const staked = this.stakedAmount.get(sender);
424
+ if (staked < amount) {
425
+ throw new Revert('Not enough staked');
426
+ }
427
+
428
+ this.stakedAmount.set(sender, SafeMath.sub(staked, amount));
429
+
430
+ return new BytesWriter(0);
431
+ }
432
+
433
+ public claimRewards(_calldata: Calldata): BytesWriter {
434
+ const sender = Blockchain.tx.sender;
435
+ const reward = this.calculateReward(sender);
436
+
437
+ this.rewards.set(sender, u256.Zero);
438
+ // Transfer reward...
439
+
440
+ const writer = new BytesWriter(32);
441
+ writer.writeU256(reward);
442
+ return writer;
443
+ }
444
+
445
+ private calculateReward(user: Address): u256 {
446
+ const timestamp = this.stakedTimestamp.get(user);
447
+ const currentTime = u256.fromU64(Blockchain.block.medianTime);
448
+ const duration = SafeMath.sub(currentTime, timestamp);
449
+ const staked = this.stakedAmount.get(user);
450
+
451
+ // Simplified: staked * duration / YEAR_IN_SECONDS
452
+ const YEAR_SECONDS = u256.fromU64(31536000);
453
+ return SafeMath.div(SafeMath.mul(staked, duration), YEAR_SECONDS);
454
+ }
455
+
456
+ public getStakeInfo(calldata: Calldata): BytesWriter {
457
+ const user = calldata.readAddress();
458
+
459
+ const writer = new BytesWriter(96);
460
+ writer.writeU256(this.stakedAmount.get(user));
461
+ writer.writeU256(this.stakedTimestamp.get(user));
462
+ writer.writeU256(this.rewards.get(user));
463
+ return writer;
464
+ }
465
+ }
466
+ ```
467
+
468
+ ## Usage Examples
469
+
470
+ ### Basic Balance Tracking
471
+
472
+ ```typescript
473
+ @final
474
+ export class Token extends OP_NET {
475
+ private balancesPointer: u16 = Blockchain.nextPointer;
476
+ private balances: AddressMemoryMap;
477
+
478
+ constructor() {
479
+ super();
480
+ this.balances = new AddressMemoryMap(this.balancesPointer);
481
+ }
482
+
483
+ public balanceOf(calldata: Calldata): BytesWriter {
484
+ const account = calldata.readAddress();
485
+ const balance = this.balances.get(account);
486
+
487
+ const writer = new BytesWriter(32);
488
+ writer.writeU256(balance);
489
+ return writer;
490
+ }
491
+
492
+ public _transfer(from: Address, to: Address, amount: u256): void {
493
+ const fromBalance = this.balances.get(from);
494
+ if (fromBalance < amount) {
495
+ throw new Revert('Insufficient balance');
496
+ }
497
+
498
+ this.balances.set(from, SafeMath.sub(fromBalance, amount));
499
+ this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
500
+ }
501
+ }
502
+ ```
503
+
504
+ ### Approval Tracking
505
+
506
+ ```typescript
507
+ import { encodePointer } from '@btc-vision/btc-runtime/runtime';
508
+
509
+ // Nested mapping: owner => (spender => amount)
510
+ // Using composite storage
511
+
512
+ private allowancesPointer: u16 = Blockchain.nextPointer;
513
+
514
+ // For nested maps, create helper methods
515
+ private getAllowance(owner: Address, spender: Address): u256 {
516
+ const subPointer = this.computeAllowanceKey(owner, spender);
517
+ const pointerHash = encodePointer(this.allowancesPointer, subPointer.toUint8Array(true));
518
+ const stored = Blockchain.getStorageAt(pointerHash);
519
+ return u256.fromUint8ArrayBE(stored);
520
+ }
521
+
522
+ private setAllowance(owner: Address, spender: Address, amount: u256): void {
523
+ const subPointer = this.computeAllowanceKey(owner, spender);
524
+ const pointerHash = encodePointer(this.allowancesPointer, subPointer.toUint8Array(true));
525
+ Blockchain.setStorageAt(pointerHash, amount.toUint8Array(true));
526
+ }
527
+
528
+ private computeAllowanceKey(owner: Address, spender: Address): u256 {
529
+ const combined = new Uint8Array(64);
530
+ combined.set(owner.toBytes(), 0);
531
+ combined.set(spender.toBytes(), 32);
532
+ return u256.fromBytes(Blockchain.sha256(combined));
533
+ }
534
+ ```
535
+
536
+ ### Staking with Multiple Values
537
+
538
+ ```typescript
539
+ // Track staked amount and timestamp per user
540
+ private stakedAmountPointer: u16 = Blockchain.nextPointer;
541
+ private stakedTimePointer: u16 = Blockchain.nextPointer;
542
+
543
+ private stakedAmount: AddressMemoryMap;
544
+ private stakedTime: AddressMemoryMap;
545
+
546
+ constructor() {
547
+ super();
548
+ this.stakedAmount = new AddressMemoryMap(this.stakedAmountPointer);
549
+ this.stakedTime = new AddressMemoryMap(this.stakedTimePointer);
550
+ }
551
+
552
+ public stake(calldata: Calldata): BytesWriter {
553
+ const amount = calldata.readU256();
554
+ const sender = Blockchain.tx.sender;
555
+
556
+ // Update staked amount
557
+ const current = this.stakedAmount.get(sender);
558
+ this.stakedAmount.set(sender, SafeMath.add(current, amount));
559
+
560
+ // Update stake time
561
+ this.stakedTime.set(sender, u256.fromU64(Blockchain.block.medianTime));
562
+
563
+ return new BytesWriter(0);
564
+ }
565
+
566
+ public getStakeInfo(calldata: Calldata): BytesWriter {
567
+ const user = calldata.readAddress();
568
+
569
+ const writer = new BytesWriter(64);
570
+ writer.writeU256(this.stakedAmount.get(user));
571
+ writer.writeU256(this.stakedTime.get(user));
572
+ return writer;
573
+ }
574
+ ```
575
+
576
+ ## Storage vs Memory
577
+
578
+ ### Storage (Persistent)
579
+
580
+ ```typescript
581
+ // AddressMemoryMap wraps persistent storage
582
+ // Changes persist across transactions
583
+
584
+ public deposit(calldata: Calldata): BytesWriter {
585
+ const amount = calldata.readU256();
586
+ const sender = Blockchain.tx.sender;
587
+
588
+ const current = this.deposits.get(sender); // Reads from storage
589
+ this.deposits.set(sender, SafeMath.add(current, amount)); // Writes to storage
590
+
591
+ return new BytesWriter(0);
592
+ }
593
+ ```
594
+
595
+ ### In-Memory Collections
596
+
597
+ ```typescript
598
+ // For temporary collections within a single call
599
+ // Use standard AssemblyScript Map
600
+
601
+ public processAddresses(calldata: Calldata): BytesWriter {
602
+ const addresses = calldata.readAddressArray();
603
+
604
+ // Temporary map for deduplication
605
+ const seen = new Map<string, bool>();
606
+
607
+ for (let i = 0; i < addresses.length; i++) {
608
+ const addrStr = addresses[i].toBytes().toString();
609
+ if (seen.has(addrStr)) {
610
+ continue; // Skip duplicate
611
+ }
612
+ seen.set(addrStr, true);
613
+
614
+ // Process unique address...
615
+ }
616
+
617
+ return new BytesWriter(0);
618
+ }
619
+ ```
620
+
621
+ ## Warning: AssemblyScript Map vs btc-runtime Map
622
+
623
+ When working with persistent storage, always use the btc-runtime Map:
624
+
625
+ ```mermaid
626
+ ---
627
+ config:
628
+ theme: dark
629
+ ---
630
+ flowchart LR
631
+ A["WRONG:<br/>AssemblyScript Map"] --> B["NOT blockchain-optimized"]
632
+ B --> C["Broken comparisons"]
633
+ C --> D["Data corruption"]
634
+ E["CORRECT:<br/>btc-runtime Map"] --> F["Blockchain-optimized"]
635
+ F --> G["Proper equality"]
636
+ G --> H["Safe operations"]
637
+ ```
638
+
639
+ ## Patterns
640
+
641
+ ### Enumerable Map
642
+
643
+ To track all keys in a map:
644
+
645
+ ```typescript
646
+ // Combine map with array for enumeration
647
+ private balancesPointer: u16 = Blockchain.nextPointer;
648
+ private holdersPointer: u16 = Blockchain.nextPointer;
649
+
650
+ private balances: AddressMemoryMap;
651
+ private holders: StoredAddressArray;
652
+
653
+ constructor() {
654
+ super();
655
+ this.balances = new AddressMemoryMap(this.balancesPointer);
656
+ this.holders = new StoredAddressArray(this.holdersPointer);
657
+ }
658
+
659
+ public _mint(to: Address, amount: u256): void {
660
+ // Track new holder
661
+ if (this.balances.get(to).isZero()) {
662
+ this.holders.push(to);
663
+ }
664
+
665
+ // Update balance
666
+ this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
667
+ }
668
+
669
+ public getHolders(_calldata: Calldata): BytesWriter {
670
+ const count = this.holders.length;
671
+ const writer = new BytesWriter(32 * i32(count) + 4);
672
+
673
+ writer.writeU32(u32(count));
674
+ for (let i: u64 = 0; i < count; i++) {
675
+ writer.writeAddress(this.holders.get(i));
676
+ }
677
+
678
+ return writer;
679
+ }
680
+ ```
681
+
682
+ ### Lazy Initialization
683
+
684
+ ```typescript
685
+ // Values initialize to default when first accessed
686
+ public ensureAccount(addr: Address): void {
687
+ // get() returns default (u256.Zero) if not set
688
+ // No explicit initialization needed
689
+ const balance = this.balances.get(addr);
690
+
691
+ // First set creates the storage entry
692
+ if (balance.isZero()) {
693
+ // Optional: Initialize with some value
694
+ this.balances.set(addr, u256.One); // e.g., welcome bonus
695
+ }
696
+ }
697
+ ```
698
+
699
+ ### Read-Modify-Write Pattern
700
+
701
+ ```typescript
702
+ public addToBalance(addr: Address, amount: u256): void {
703
+ // Read current
704
+ const current = this.balances.get(addr);
705
+
706
+ // Modify
707
+ const newBalance = SafeMath.add(current, amount);
708
+
709
+ // Write back
710
+ this.balances.set(addr, newBalance);
711
+ }
712
+ ```
713
+
714
+ ## Best Practices
715
+
716
+ ### 1. Initialize in Constructor
717
+
718
+ ```typescript
719
+ constructor() {
720
+ super();
721
+ // Always initialize maps in constructor
722
+ this.balances = new AddressMemoryMap(this.balancesPointer);
723
+ this.stakes = new AddressMemoryMap(this.stakesPointer);
724
+ }
725
+ ```
726
+
727
+ ### 2. Default Values
728
+
729
+ ```typescript
730
+ // AddressMemoryMap always uses u256.Zero as the default value
731
+ // Unset addresses will return u256.Zero when queried
732
+ new AddressMemoryMap(ptr);
733
+ ```
734
+
735
+ ### 3. Validate Addresses
736
+
737
+ ```typescript
738
+ public transfer(calldata: Calldata): BytesWriter {
739
+ const to = calldata.readAddress();
740
+
741
+ // Validate before map operations
742
+ if (to.equals(Address.zero())) {
743
+ throw new Revert('Invalid recipient');
744
+ }
745
+
746
+ // Then use map
747
+ this.balances.set(to, amount);
748
+ }
749
+ ```
750
+
751
+ ### 4. Consider Overflow
752
+
753
+ ```typescript
754
+ // Always use SafeMath when updating values
755
+ const current = this.balances.get(addr);
756
+ const newValue = SafeMath.add(current, amount); // Checks overflow
757
+ this.balances.set(addr, newValue);
758
+ ```
759
+
760
+ ---
761
+
762
+ **Navigation:**
763
+ - Previous: [Stored Maps](./stored-maps.md)
764
+ - Next: [Cross-Contract Calls](../advanced/cross-contract-calls.md)