@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,652 @@
1
+ # Events
2
+
3
+ Events in OPNet allow contracts to emit signals that can be observed by off-chain systems. They're essential for tracking state changes, building user interfaces, and indexing blockchain data.
4
+
5
+ ## Overview
6
+
7
+ Events are:
8
+ - **Write-only** - Contracts can emit but not read events
9
+ - **Indexed** - Off-chain systems can filter and search events
10
+ - **Size-limited** - Maximum 352 bytes per event
11
+ - **Lightweight** - Less overhead than storage writes
12
+
13
+ ## Creating Events
14
+
15
+ ### Event Class Hierarchy
16
+
17
+ ```mermaid
18
+ classDiagram
19
+ class NetEvent {
20
+ <<abstract>>
21
+ +eventType: string
22
+ #data: BytesWriter
23
+ +length: u32
24
+ +getEventData() Uint8Array
25
+ }
26
+
27
+ class TransferredEvent {
28
+ +constructor(operator, from, to, amount)
29
+ eventType = 'Transferred'
30
+ }
31
+
32
+ class TransferredSingleEvent {
33
+ +constructor(operator, from, to, id, value)
34
+ eventType = 'TransferredSingle'
35
+ }
36
+
37
+ class TransferredBatchEvent {
38
+ +constructor(operator, from, to, ids, values)
39
+ eventType = 'TransferredBatch'
40
+ }
41
+
42
+ class ApprovedEvent {
43
+ +constructor(owner, spender, amount)
44
+ eventType = 'Approved'
45
+ }
46
+
47
+ class ApprovedForAllEvent {
48
+ +constructor(account, operator, approved)
49
+ eventType = 'ApprovedForAll'
50
+ }
51
+
52
+ class MintedEvent {
53
+ +constructor(to, amount)
54
+ eventType = 'Minted'
55
+ }
56
+
57
+ class BurnedEvent {
58
+ +constructor(from, amount)
59
+ eventType = 'Burned'
60
+ }
61
+
62
+ class URIEvent {
63
+ +constructor(value, id)
64
+ eventType = 'URI'
65
+ }
66
+
67
+ class CustomEvent {
68
+ +constructor(...)
69
+ }
70
+
71
+ NetEvent <|-- TransferredEvent
72
+ NetEvent <|-- TransferredSingleEvent
73
+ NetEvent <|-- TransferredBatchEvent
74
+ NetEvent <|-- ApprovedEvent
75
+ NetEvent <|-- ApprovedForAllEvent
76
+ NetEvent <|-- MintedEvent
77
+ NetEvent <|-- BurnedEvent
78
+ NetEvent <|-- URIEvent
79
+ NetEvent <|-- CustomEvent
80
+ ```
81
+
82
+ ### Using Predefined Events
83
+
84
+ OPNet provides common events out of the box:
85
+
86
+ ```typescript
87
+ import {
88
+ TransferredEvent,
89
+ TransferredSingleEvent,
90
+ TransferredBatchEvent,
91
+ ApprovedEvent,
92
+ ApprovedForAllEvent,
93
+ MintedEvent,
94
+ BurnedEvent,
95
+ URIEvent,
96
+ } from '@btc-vision/btc-runtime/runtime';
97
+
98
+ // Emit a transfer event (operator is the caller initiating the transfer)
99
+ // Event type: 'Transferred'
100
+ this.emitEvent(new TransferredEvent(operator, from, to, amount));
101
+
102
+ // Emit a single token transfer (ERC1155-style)
103
+ // Event type: 'TransferredSingle'
104
+ this.emitEvent(new TransferredSingleEvent(operator, from, to, tokenId, amount));
105
+
106
+ // Emit a batch token transfer (ERC1155-style, max 3 items)
107
+ // Event type: 'TransferredBatch'
108
+ this.emitEvent(new TransferredBatchEvent(operator, from, to, tokenIds, amounts));
109
+
110
+ // Emit an approval event
111
+ // Event type: 'Approved'
112
+ this.emitEvent(new ApprovedEvent(owner, spender, amount));
113
+
114
+ // Emit an operator approval event
115
+ // Event type: 'ApprovedForAll'
116
+ this.emitEvent(new ApprovedForAllEvent(account, operator, true));
117
+
118
+ // Emit a mint event
119
+ // Event type: 'Minted'
120
+ this.emitEvent(new MintedEvent(to, amount));
121
+
122
+ // Emit a burn event
123
+ // Event type: 'Burned'
124
+ this.emitEvent(new BurnedEvent(from, amount));
125
+
126
+ // Emit a URI event (max 200 bytes for URI)
127
+ // Event type: 'URI'
128
+ this.emitEvent(new URIEvent('https://example.com/token/1', tokenId));
129
+ ```
130
+
131
+ ### Custom Events
132
+
133
+ Create custom events by extending `NetEvent`:
134
+
135
+ ```typescript
136
+ import { NetEvent, BytesWriter, Address, ADDRESS_BYTE_LENGTH, U256_BYTE_LENGTH } from '@btc-vision/btc-runtime/runtime';
137
+ import { u256 } from '@btc-vision/as-bignum/assembly';
138
+
139
+ @final
140
+ export class StakeEvent extends NetEvent {
141
+ public constructor(
142
+ staker: Address,
143
+ amount: u256,
144
+ duration: u64
145
+ ) {
146
+ // Create BytesWriter with appropriate size
147
+ const data: BytesWriter = new BytesWriter(ADDRESS_BYTE_LENGTH + U256_BYTE_LENGTH + 8);
148
+ data.writeAddress(staker);
149
+ data.writeU256(amount);
150
+ data.writeU64(duration);
151
+
152
+ super('Stake', data);
153
+ }
154
+ }
155
+ ```
156
+
157
+ **Solidity Comparison:**
158
+
159
+ ```solidity
160
+ // Solidity
161
+ event Stake(address indexed staker, uint256 amount, uint64 duration);
162
+ ```
163
+
164
+ ```typescript
165
+ // OPNet
166
+ @final
167
+ export class StakeEvent extends NetEvent {
168
+ public constructor(staker: Address, amount: u256, duration: u64) {
169
+ const data: BytesWriter = new BytesWriter(ADDRESS_BYTE_LENGTH + U256_BYTE_LENGTH + 8);
170
+ data.writeAddress(staker);
171
+ data.writeU256(amount);
172
+ data.writeU64(duration);
173
+ super('Stake', data);
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### Emitting Events
179
+
180
+ ```typescript
181
+ // In your contract method
182
+ @method()
183
+ public stake(calldata: Calldata): BytesWriter {
184
+ const amount = calldata.readU256();
185
+ const duration = calldata.readU64();
186
+
187
+ // ... staking logic ...
188
+
189
+ // Emit event
190
+ this.emitEvent(new StakeEvent(
191
+ Blockchain.tx.sender,
192
+ amount,
193
+ duration
194
+ ));
195
+
196
+ return new BytesWriter(0);
197
+ }
198
+ ```
199
+
200
+ ### Event Emission Flow
201
+
202
+ ```mermaid
203
+ sequenceDiagram
204
+ participant Contract
205
+ participant EventClass as Event Class Instance
206
+ participant BytesWriter
207
+ participant Blockchain
208
+ participant EventLog as Event Log (Off-chain)
209
+
210
+ Contract->>EventClass: new TransferEvent(from, to, amount)
211
+ activate EventClass
212
+ Note over EventClass: Constructor builds data
213
+
214
+ EventClass->>BytesWriter: new BytesWriter(size)
215
+ EventClass->>BytesWriter: writeAddress(from)
216
+ EventClass->>BytesWriter: writeAddress(to)
217
+ EventClass->>BytesWriter: writeU256(amount)
218
+ EventClass->>EventClass: super('Transfer', data)
219
+ Note over EventClass: Validates size <= 352 bytes
220
+ EventClass-->>Contract: Return event instance
221
+ deactivate EventClass
222
+
223
+ Contract->>Contract: this.emitEvent(event)
224
+ activate Contract
225
+
226
+ Contract->>Blockchain: Blockchain.emit(event)
227
+ activate Blockchain
228
+ Blockchain->>EventClass: event.getEventData()
229
+ EventClass-->>Blockchain: Return encoded bytes
230
+
231
+ Note over Blockchain: Format: [4B typeLen][type][4B dataLen][data]
232
+ Blockchain->>EventLog: Store in transaction receipt
233
+
234
+ EventLog-->>Blockchain: Event logged
235
+ Blockchain-->>Contract: Success
236
+ deactivate Blockchain
237
+ deactivate Contract
238
+
239
+ Note over EventLog: Event available for<br/>off-chain indexing
240
+ ```
241
+
242
+ ## Event Structure
243
+
244
+ Every event has:
245
+
246
+ | Component | Description |
247
+ |-----------|-------------|
248
+ | **Event Type** | String identifier (e.g., "Transferred", "Approved", "Minted", "Burned") |
249
+ | **Data** | Encoded event parameters |
250
+ | **Contract** | Address of emitting contract (automatic) |
251
+
252
+ ### Encoding
253
+
254
+ Events are encoded using `BytesWriter`:
255
+
256
+ ```typescript
257
+ @final
258
+ export class MyEvent extends NetEvent {
259
+ public constructor(
260
+ value1: u256,
261
+ value2: Address,
262
+ value3: bool
263
+ ) {
264
+ // Calculate size: 32 (u256) + 32 (Address) + 1 (bool) = 65 bytes
265
+ const data: BytesWriter = new BytesWriter(65);
266
+ data.writeU256(value1); // 32 bytes
267
+ data.writeAddress(value2); // 32 bytes
268
+ data.writeBoolean(value3); // 1 byte
269
+
270
+ super('MyEvent', data); // Event type name and data
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### Event Creation Flow
276
+
277
+ ```mermaid
278
+ ---
279
+ config:
280
+ theme: dark
281
+ ---
282
+ flowchart LR
283
+ Start["Create Event"] --> Init["Create BytesWriter with size"]
284
+ Init --> Write["Write data to BytesWriter"]
285
+ Write --> Check{"Size <= 352 bytes?"}
286
+ Check -->|Yes| Super["Call super(eventType, data)"]
287
+ Check -->|No| Revert["Constructor throws Revert"]
288
+ Super --> Emit["emitEvent(event)"]
289
+ Emit --> Index["Indexed Off-chain"]
290
+ ```
291
+
292
+ ## Size Limit (352 Bytes)
293
+
294
+ **CRITICAL:** Events cannot exceed 352 bytes of encoded data.
295
+
296
+ ```typescript
297
+ // Calculate your event size
298
+ // Address: 32 bytes
299
+ // u256: 32 bytes
300
+ // u64: 8 bytes
301
+ // bool: 1 byte
302
+ // string: length + content
303
+
304
+ @final
305
+ export class LargeEvent extends NetEvent {
306
+ public constructor(
307
+ addr1: Address, addr2: Address, addr3: Address,
308
+ amount1: u256, amount2: u256, amount3: u256,
309
+ amount4: u256, amount5: u256, amount6: u256,
310
+ timestamp: u64
311
+ ) {
312
+ // Calculate: 3 addresses (96) + 6 u256 (192) + 1 u64 (8) = 296 bytes - OK!
313
+ const data: BytesWriter = new BytesWriter(296);
314
+ data.writeAddress(addr1); // 32 bytes
315
+ data.writeAddress(addr2); // 32 bytes
316
+ data.writeAddress(addr3); // 32 bytes
317
+ data.writeU256(amount1); // 32 bytes
318
+ data.writeU256(amount2); // 32 bytes
319
+ data.writeU256(amount3); // 32 bytes
320
+ data.writeU256(amount4); // 32 bytes
321
+ data.writeU256(amount5); // 32 bytes
322
+ data.writeU256(amount6); // 32 bytes
323
+ data.writeU64(timestamp); // 8 bytes
324
+ super('LargeEvent', data);
325
+ }
326
+ }
327
+ ```
328
+
329
+ ### What If You Exceed the Limit?
330
+
331
+ ```typescript
332
+ // This will fail at runtime with "Event data length exceeds maximum length."
333
+ @final
334
+ export class TooLargeEvent extends NetEvent {
335
+ public constructor(values: u256[]) {
336
+ // Writing 11 x u256 = 352 bytes
337
+ // This is the absolute maximum!
338
+ const data: BytesWriter = new BytesWriter(352);
339
+ for (let i = 0; i < 11; i++) {
340
+ data.writeU256(values[i]);
341
+ }
342
+ super('TooLarge', data); // Will throw if data exceeds 352 bytes
343
+ }
344
+ }
345
+ ```
346
+
347
+ **Solutions:**
348
+ 1. Split into multiple events
349
+ 2. Only include essential data
350
+ 3. Use shorter encodings where possible
351
+
352
+ ## Predefined Events
353
+
354
+ ### TransferredEvent
355
+
356
+ ```typescript
357
+ // Emitted on token transfers
358
+ // Event type: 'Transferred'
359
+ new TransferredEvent(operator: Address, from: Address, to: Address, amount: u256)
360
+
361
+ // Fields:
362
+ // - operator: address initiating the transfer
363
+ // - from: sender address
364
+ // - to: recipient address
365
+ // - amount: tokens transferred
366
+ ```
367
+
368
+ ### TransferredSingleEvent
369
+
370
+ ```typescript
371
+ // Emitted on single token transfers (ERC1155-style)
372
+ // Event type: 'TransferredSingle'
373
+ new TransferredSingleEvent(operator: Address, from: Address, to: Address, id: u256, value: u256)
374
+
375
+ // Fields:
376
+ // - operator: address initiating the transfer
377
+ // - from: sender address
378
+ // - to: recipient address
379
+ // - id: token ID
380
+ // - value: amount transferred
381
+ ```
382
+
383
+ ### TransferredBatchEvent
384
+
385
+ ```typescript
386
+ // Emitted on batch token transfers (ERC1155-style)
387
+ // Event type: 'TransferredBatch'
388
+ // Limited to 3 items due to 352-byte event size limit
389
+ new TransferredBatchEvent(operator: Address, from: Address, to: Address, ids: u256[], values: u256[])
390
+
391
+ // Fields:
392
+ // - operator: address initiating the transfer
393
+ // - from: sender address
394
+ // - to: recipient address
395
+ // - ids: array of token IDs (max 3)
396
+ // - values: array of amounts (max 3)
397
+ ```
398
+
399
+ ### ApprovedEvent
400
+
401
+ ```typescript
402
+ // Emitted on approval changes
403
+ // Event type: 'Approved'
404
+ new ApprovedEvent(owner: Address, spender: Address, amount: u256)
405
+
406
+ // Fields:
407
+ // - owner: token owner
408
+ // - spender: approved spender
409
+ // - amount: approved amount
410
+ ```
411
+
412
+ ### ApprovedForAllEvent
413
+
414
+ ```typescript
415
+ // Emitted on operator approval changes
416
+ // Event type: 'ApprovedForAll'
417
+ new ApprovedForAllEvent(account: Address, operator: Address, approved: boolean)
418
+
419
+ // Fields:
420
+ // - account: token owner granting approval
421
+ // - operator: address being approved/revoked
422
+ // - approved: true if approved, false if revoked
423
+ ```
424
+
425
+ ### MintedEvent
426
+
427
+ ```typescript
428
+ // Emitted when tokens are minted
429
+ // Event type: 'Minted'
430
+ new MintedEvent(to: Address, amount: u256)
431
+
432
+ // Fields:
433
+ // - to: recipient of minted tokens
434
+ // - amount: tokens minted
435
+ ```
436
+
437
+ ### BurnedEvent
438
+
439
+ ```typescript
440
+ // Emitted when tokens are burned
441
+ // Event type: 'Burned'
442
+ new BurnedEvent(from: Address, amount: u256)
443
+
444
+ // Fields:
445
+ // - from: address tokens burned from
446
+ // - amount: tokens burned
447
+ ```
448
+
449
+ ### URIEvent
450
+
451
+ ```typescript
452
+ // Emitted when token URI is updated
453
+ // Event type: 'URI'
454
+ // URI length is limited to 200 bytes
455
+ new URIEvent(value: string, id: u256)
456
+
457
+ // Fields:
458
+ // - value: URI string (max 200 bytes)
459
+ // - id: token ID
460
+ ```
461
+
462
+ ## Solidity Comparison
463
+
464
+ ### Event Declaration
465
+
466
+ ```solidity
467
+ // Solidity
468
+ event Transfer(address indexed from, address indexed to, uint256 value);
469
+ ```
470
+
471
+ ```typescript
472
+ // OPNet - uses predefined TransferredEvent with operator field
473
+ // Event type: 'Transferred'
474
+ import { TransferredEvent } from '@btc-vision/btc-runtime/runtime';
475
+
476
+ // Or create a custom transfer event without operator:
477
+ @final
478
+ export class TransferEvent extends NetEvent {
479
+ public constructor(from: Address, to: Address, value: u256) {
480
+ const data: BytesWriter = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH);
481
+ data.writeAddress(from);
482
+ data.writeAddress(to);
483
+ data.writeU256(value);
484
+ super('Transfer', data);
485
+ }
486
+ }
487
+ ```
488
+
489
+ ### Emitting Events
490
+
491
+ ```solidity
492
+ // Solidity
493
+ emit Transfer(from, to, amount);
494
+ ```
495
+
496
+ ```typescript
497
+ // OPNet - using predefined TransferredEvent (includes operator)
498
+ this.emitEvent(new TransferredEvent(Blockchain.tx.sender, from, to, amount));
499
+
500
+ // Or using custom TransferEvent (without operator)
501
+ this.emitEvent(new TransferEvent(from, to, amount));
502
+ ```
503
+
504
+ ### Indexed Parameters
505
+
506
+ ```solidity
507
+ // Solidity: indexed parameters for filtering
508
+ event Transfer(address indexed from, address indexed to, uint256 value);
509
+
510
+ // OPNet: All parameters can be filtered by off-chain indexers
511
+ // (no explicit "indexed" keyword needed)
512
+ ```
513
+
514
+ ## Best Practices
515
+
516
+ ### 1. Event for Every State Change
517
+
518
+ ```typescript
519
+ @method()
520
+ @emit('Transferred') // Decorator documents which event this method emits
521
+ public transfer(calldata: Calldata): BytesWriter {
522
+ const to = calldata.readAddress();
523
+ const amount = calldata.readU256();
524
+ const from = Blockchain.tx.sender;
525
+
526
+ // _transfer internally emits TransferredEvent via createTransferredEvent
527
+ this._transfer(from, to, amount);
528
+
529
+ return new BytesWriter(0);
530
+ }
531
+
532
+ // For custom events in your own methods:
533
+ @method()
534
+ @emit('Staked')
535
+ public stake(calldata: Calldata): BytesWriter {
536
+ const amount = calldata.readU256();
537
+
538
+ // Update state first
539
+ this._stakes.set(Blockchain.tx.sender, amount);
540
+
541
+ // Then emit event
542
+ this.emitEvent(new StakedEvent(Blockchain.tx.sender, amount));
543
+
544
+ return new BytesWriter(0);
545
+ }
546
+ ```
547
+
548
+ ### 2. Meaningful Event Names
549
+
550
+ ```typescript
551
+ // Good: Descriptive names
552
+ @final class TokenStaked extends NetEvent { /* ... */ }
553
+ @final class RewardsClaimed extends NetEvent { /* ... */ }
554
+ @final class PoolCreated extends NetEvent { /* ... */ }
555
+
556
+ // Bad: Generic names
557
+ @final class Event1 extends NetEvent { /* ... */ }
558
+ @final class DataChanged extends NetEvent { /* ... */ }
559
+ ```
560
+
561
+ ### 3. Include Context
562
+
563
+ ```typescript
564
+ // Good: Include relevant context
565
+ @final
566
+ class SwapExecuted extends NetEvent {
567
+ public constructor(
568
+ user: Address,
569
+ tokenIn: Address,
570
+ tokenOut: Address,
571
+ amountIn: u256,
572
+ amountOut: u256,
573
+ timestamp: u64
574
+ ) {
575
+ const data: BytesWriter = new BytesWriter(ADDRESS_BYTE_LENGTH * 3 + U256_BYTE_LENGTH * 2 + 8);
576
+ data.writeAddress(user);
577
+ data.writeAddress(tokenIn);
578
+ data.writeAddress(tokenOut);
579
+ data.writeU256(amountIn);
580
+ data.writeU256(amountOut);
581
+ data.writeU64(timestamp);
582
+ super('SwapExecuted', data);
583
+ }
584
+ }
585
+
586
+ // Bad: Missing context
587
+ @final
588
+ class Swap extends NetEvent {
589
+ public constructor(amount: u256) {
590
+ const data: BytesWriter = new BytesWriter(U256_BYTE_LENGTH);
591
+ data.writeU256(amount);
592
+ super('Swap', data);
593
+ }
594
+ }
595
+ ```
596
+
597
+ ### 4. Check Size Before Deployment
598
+
599
+ ```typescript
600
+ // Test your event sizes - event.length returns the data size
601
+ function testEventSize(): void {
602
+ const event = new MyEvent(/* max size parameters */);
603
+
604
+ // The event validates size in the constructor
605
+ // If it exceeds 352 bytes, it will throw a Revert error
606
+ assert(event.length <= 352, 'Event exceeds size limit');
607
+ }
608
+ ```
609
+
610
+ ## Multiple Events
611
+
612
+ You can emit multiple events in a single transaction:
613
+
614
+ ```typescript
615
+ @method()
616
+ @emit('Burned')
617
+ @emit('Minted')
618
+ @emit('FeeCollected')
619
+ public complexOperation(calldata: Calldata): BytesWriter {
620
+ // ... logic ...
621
+
622
+ // Emit multiple events using predefined events
623
+ this.emitEvent(new BurnedEvent(from, burnAmount));
624
+ this.emitEvent(new MintedEvent(to, mintAmount));
625
+ this.emitEvent(new FeeCollectedEvent(feeRecipient, feeAmount));
626
+
627
+ return new BytesWriter(0);
628
+ }
629
+ ```
630
+
631
+ ## Listening to Events (Off-chain)
632
+
633
+ Events are indexed and can be queried:
634
+
635
+ ```typescript
636
+ // Off-chain code (not AssemblyScript)
637
+ const events = await indexer.getEvents({
638
+ contract: tokenAddress,
639
+ eventType: 'Transferred', // Use the event type string from the event class
640
+ fromBlock: 100000,
641
+ toBlock: 'latest',
642
+ filter: {
643
+ from: userAddress
644
+ }
645
+ });
646
+ ```
647
+
648
+ ---
649
+
650
+ **Navigation:**
651
+ - Previous: [Pointers](./pointers.md)
652
+ - Next: [Security](./security.md)