@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.
- package/LICENSE +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +840 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +446 -0
- package/package.json +52 -27
- package/runtime/memory/MapOfMap.ts +1 -0
- 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)
|