UNPKG

@btc-vision/btc-runtime

Version:

Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.

643 lines (517 loc) 16.1 kB
# Events API Reference Events provide a way to emit state change notifications that off-chain applications can monitor. ## Import ```typescript import { NetEvent, TransferredEvent, ApprovedEvent, MintedEvent, BurnedEvent, TransferredSingleEvent, TransferredBatchEvent, ApprovedForAllEvent, URIEvent, BytesWriter, } from '@btc-vision/btc-runtime/runtime'; ``` ## NetEvent Base Class All events extend the `NetEvent` base class. ```typescript abstract class NetEvent { protected constructor(eventType: string, data: BytesWriter) public getEventData(): Uint8Array public get length(): u32 public readonly eventType: string } ``` ## Event Class Hierarchy ```mermaid classDiagram class NetEvent { <<abstract>> +string eventType #BytesWriter data -Uint8Array buffer #constructor(eventType: string, data: BytesWriter) +getEventData() Uint8Array +get length() u32 } class TransferredEvent { +constructor(operator, from, to, amount) } class TransferredSingleEvent { +constructor(operator, from, to, id, value) } class TransferredBatchEvent { +constructor(operator, from, to, ids, values) } class ApprovedEvent { +constructor(owner, spender, amount) } class ApprovedForAllEvent { +constructor(account, operator, approved) } class MintedEvent { +constructor(to, amount) } class BurnedEvent { +constructor(from, amount) } class URIEvent { +constructor(value, id) } class CustomEvent { +constructor(...) } NetEvent <|-- TransferredEvent NetEvent <|-- TransferredSingleEvent NetEvent <|-- TransferredBatchEvent NetEvent <|-- ApprovedEvent NetEvent <|-- ApprovedForAllEvent NetEvent <|-- MintedEvent NetEvent <|-- BurnedEvent NetEvent <|-- URIEvent NetEvent <|-- CustomEvent note for NetEvent "All events must extend NetEvent\nand build data in constructor" note for CustomEvent "User-defined events\nfor custom contract logic" ``` ## Creating Custom Events ### Basic Event ```typescript @final class MyEvent extends NetEvent { constructor(value: u256) { const data = new BytesWriter(32); data.writeU256(value); super('MyEvent', data); } } // Emit this.emitEvent(new MyEvent(u256.fromU64(100))); ``` The following diagram shows the event system architecture: ```mermaid graph LR subgraph "Event Creation" A[Contract Method] -->|Create| B[NetEvent Instance] B -->|Extends| C[NetEvent Base Class] C --> D[eventType: string] C --> E[BytesWriter data] end subgraph "Validation" E --> F{data.length<br/>352 bytes?} F -->|Yes| G[Valid Event] F -->|No| H[Revert:<br/>Too Large] end subgraph "Emission" G --> I[contract.emitEvent] I --> J[Blockchain.emit] J --> K[Event Binary<br/>Format] K --> L[Transaction<br/>Receipt] end ``` ### Multi-Field Event ```typescript import { ADDRESS_BYTE_LENGTH, U256_BYTE_LENGTH } from '@btc-vision/btc-runtime/runtime'; @final class TransferEvent extends NetEvent { constructor(from: Address, to: Address, amount: u256) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH); data.writeAddress(from); data.writeAddress(to); data.writeU256(amount); super('Transfer', data); } } ``` ### Event with Data Constructor ```typescript @final class ComplexEvent extends NetEvent { constructor(user: Address, action: u8, timestamp: u64, value: u256) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH + 1 + 8 + U256_BYTE_LENGTH); data.writeAddress(user); data.writeU8(action); data.writeU64(timestamp); data.writeU256(value); super('ComplexEvent', data); } } ``` ## Predefined Events ### TransferredEvent Standard token transfer event. Event type: `'Transferred'`. Note: OP20 transfers include an operator field. ```typescript class TransferredEvent extends NetEvent { constructor( operator: Address, // The address initiating the transfer from: Address, // The sender address to: Address, // The recipient address amount: u256 // The amount transferred ) } ``` ```typescript this.emitEvent(new TransferredEvent(Blockchain.tx.sender, sender, recipient, amount)); ``` ### TransferredSingleEvent Single token transfer event for ERC1155-style tokens. Event type: `'TransferredSingle'`. ```typescript class TransferredSingleEvent extends NetEvent { constructor( operator: Address, // The address initiating the transfer from: Address, // The sender address to: Address, // The recipient address id: u256, // The token ID value: u256 // The amount transferred ) } ``` ```typescript this.emitEvent(new TransferredSingleEvent(Blockchain.tx.sender, from, to, tokenId, amount)); ``` ### TransferredBatchEvent Batch token transfer event for ERC1155-style tokens. Event type: `'TransferredBatch'`. Limited to 3 items due to the 352-byte event size limit. ```typescript class TransferredBatchEvent extends NetEvent { constructor( operator: Address, // The address initiating the transfer from: Address, // The sender address to: Address, // The recipient address ids: u256[], // The token IDs (max 3 items) values: u256[] // The amounts transferred (max 3 items) ) } ``` ```typescript this.emitEvent(new TransferredBatchEvent(Blockchain.tx.sender, from, to, tokenIds, amounts)); ``` ### ApprovedEvent Standard approval event. Event type: `'Approved'`. ```typescript class ApprovedEvent extends NetEvent { constructor( owner: Address, // The token owner spender: Address, // The approved spender amount: u256 // The approved amount ) } ``` ```typescript this.emitEvent(new ApprovedEvent(owner, spender, allowance)); ``` ### ApprovedForAllEvent Operator approval event for all tokens. Event type: `'ApprovedForAll'`. ```typescript class ApprovedForAllEvent extends NetEvent { constructor( account: Address, // The account granting approval operator: Address, // The approved operator approved: boolean // Whether approval is granted or revoked ) } ``` ```typescript this.emitEvent(new ApprovedForAllEvent(owner, operator, true)); ``` ### MintedEvent Token minting event. Event type: `'Minted'`. ```typescript class MintedEvent extends NetEvent { constructor( to: Address, // The recipient of minted tokens amount: u256 // The amount minted ) } ``` ```typescript this.emitEvent(new MintedEvent(recipient, mintAmount)); ``` ### BurnedEvent Token burning event. Event type: `'Burned'`. ```typescript class BurnedEvent extends NetEvent { constructor( from: Address, // The address tokens are burned from amount: u256 // The amount burned ) } ``` ```typescript this.emitEvent(new BurnedEvent(burner, burnAmount)); ``` ### URIEvent URI update event for token metadata. Event type: `'URI'`. URI length is limited to 200 bytes. ```typescript class URIEvent extends NetEvent { constructor( value: string, // The URI string (max 200 bytes) id: u256 // The token ID ) } ``` ```typescript this.emitEvent(new URIEvent('https://example.com/token/1', tokenId)); ``` ## Event Lifecycle The following sequence diagram shows the complete event lifecycle from creation to storage: ```mermaid sequenceDiagram participant C as Contract participant E as NetEvent participant BC as Blockchain participant R as Receipt Storage Note over C,R: Event Lifecycle C->>E: new TransferredEvent(operator, from, to, amount) E->>E: Create BytesWriter with size E->>E: writer.writeAddress(operator) E->>E: writer.writeAddress(from) E->>E: writer.writeAddress(to) E->>E: writer.writeU256(amount) E->>E: super('Transferred', data) E->>E: Validate length <= 352 bytes C->>C: this.emitEvent(event) C->>BC: Blockchain.emit(event) BC->>E: event.getEventData() E->>BC: Return encoded bytes BC->>R: Store in transaction receipt R->>R: Format: [4B:typeLen][typeStr][4B:dataLen][data] Note over C,R: Off-chain indexers can now parse events ``` ## Event Encoding Flow The following diagram shows how events are encoded into binary format: ```mermaid flowchart LR subgraph "Event Construction" A[Event Instance] --> B[Constructor<br/>Parameters] B --> C[Create BytesWriter<br/>in constructor] end subgraph "BytesWriter Encoding" C --> D[BytesWriter] D --> E[Write Address<br/>32 bytes] D --> F[Write u256<br/>32 bytes] D --> G[Write u64<br/>8 bytes] D --> H[Write u8<br/>1 byte] D --> I[Write bool<br/>1 byte] end subgraph "Validation" E & F & G & H & I --> J[Combined Buffer] J --> K{Size Check} K -->|<= 352 bytes| L[Valid Event] K -->|> 352 bytes| M[Revert] end subgraph "Serialization Format" L --> N[4B: type name length] N --> O[N bytes: type name UTF-8] O --> P[4B: data length] P --> Q[M bytes: encoded data] end ``` ## Event Size Limits Events have a maximum payload size of **352 bytes**. | Component | Size | |-----------|------| | Event type name | Variable | | Event data | Up to 352 bytes | ```typescript // Calculate event data size // Address: 32 bytes, u256: 32 bytes @final class LargeEvent extends NetEvent { constructor( addr1: Address, // 32 bytes addr2: Address, // 32 bytes value1: u256, // 32 bytes value2: u256, // 32 bytes value3: u256 // 32 bytes ) { // Total: 160 bytes - OK const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 3); data.writeAddress(addr1); data.writeAddress(addr2); data.writeU256(value1); data.writeU256(value2); data.writeU256(value3); super('LargeEvent', data); } } ``` ## Emitting Events Use `emitEvent` from the contract: ```typescript // In contract method @method( { name: 'to', type: ABIDataTypes.ADDRESS }, { name: 'amount', type: ABIDataTypes.UINT256 }, ) @emit('Transferred') public transfer(calldata: Calldata): BytesWriter { const to: Address = calldata.readAddress(); const amount: u256 = calldata.readU256(); const from: Address = Blockchain.tx.sender; // Perform transfer this._transfer(from, to, amount); // Emit event (OP_NET base class handles emitting via internal _transfer) // For custom events, use: // this.emitEvent(new TransferredEvent(Blockchain.tx.sender, from, to, amount)); return new BytesWriter(0); } ``` Or use `Blockchain.emit`: ```typescript Blockchain.emit(new TransferredEvent(Blockchain.tx.sender, from, to, amount)); ``` ## Event Encoding Format Events are encoded as: ``` [4 bytes: type name length] [N bytes: type name (UTF-8)] [4 bytes: data length] [M bytes: event data] ``` ## Common Event Patterns ### State Change Events ```typescript @final class OwnershipTransferred extends NetEvent { constructor(previousOwner: Address, newOwner: Address) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 2); data.writeAddress(previousOwner); data.writeAddress(newOwner); super('OwnershipTransferred', data); } } ``` ### Action Events ```typescript @final class Paused extends NetEvent { constructor(account: Address) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH); data.writeAddress(account); super('Paused', data); } } @final class Unpaused extends NetEvent { constructor(account: Address) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH); data.writeAddress(account); super('Unpaused', data); } } ``` ### Indexed-Style Events While OP_NET doesn't have Solidity's indexed parameters, you can structure events for efficient filtering: ```typescript @final class OrderFilled extends NetEvent { constructor( orderId: u256, maker: Address, taker: Address, amount: u256, price: u256 ) { const data = new BytesWriter(U256_BYTE_LENGTH * 3 + ADDRESS_BYTE_LENGTH * 2); // Put "indexed" fields first for consistent offset data.writeU256(orderId); data.writeAddress(maker); data.writeAddress(taker); data.writeU256(amount); data.writeU256(price); super('OrderFilled', data); } } ``` ## Event Best Practices ### 1. Emit After State Changes ```typescript // Good - emit after state is updated this._balances.set(to, newBalance); this.emitEvent(new TransferredEvent(Blockchain.tx.sender, from, to, amount)); // Bad - emit before state change this.emitEvent(new TransferredEvent(Blockchain.tx.sender, from, to, amount)); this._balances.set(to, newBalance); // Could fail ``` ### 2. Use Descriptive Event Names ```typescript // Good - clear event names @final class TokensMinted extends NetEvent { ... } @final class LiquidityAdded extends NetEvent { ... } @final class StakeWithdrawn extends NetEvent { ... } // Less clear @final class Action1 extends NetEvent { ... } @final class Update extends NetEvent { ... } ``` ### 3. Include Relevant Context ```typescript // Good - includes all relevant data @final class Swap extends NetEvent { constructor( user: Address, tokenIn: Address, tokenOut: Address, amountIn: u256, amountOut: u256 ) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 3 + U256_BYTE_LENGTH * 2); data.writeAddress(user); data.writeAddress(tokenIn); data.writeAddress(tokenOut); data.writeU256(amountIn); data.writeU256(amountOut); super('Swap', data); } } // Less useful - missing context @final class Swap extends NetEvent { constructor(amount: u256) { const data = new BytesWriter(U256_BYTE_LENGTH); data.writeU256(amount); super('Swap', data); } } ``` ### 4. Keep Events Consistent ```typescript // Use consistent field ordering across similar events @final class Deposit extends NetEvent { constructor(user: Address, token: Address, amount: u256) { const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH); data.writeAddress(user); data.writeAddress(token); data.writeU256(amount); super('Deposit', data); } } @final class Withdraw extends NetEvent { constructor(user: Address, token: Address, amount: u256) { // Same order const data = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH); data.writeAddress(user); data.writeAddress(token); data.writeU256(amount); super('Withdraw', data); } } ``` ## Solidity Comparison | Solidity | OP_NET | |----------|-------| | `event Transfer(address indexed from, address indexed to, uint256 value)` | `class TransferredEvent extends NetEvent` | | `emit Transfer(from, to, value)` | `emitEvent(new TransferredEvent(operator, from, to, value))` | | Indexed parameters | Structure data with important fields first | | Anonymous events | Not supported | ## Event Limitations 1. **No indexed parameters** - All parameters are in data payload 2. **352-byte limit** - Plan data structure carefully 3. **Not accessible on-chain** - Events are for off-chain consumption only 4. **No topics** - Single event type string instead of topic hashes --- **Navigation:** - Previous: [Storage API](./storage.md) - [Back to Documentation Index](../README.md)