@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
Markdown
# 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
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';
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
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
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
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
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
class Paused extends NetEvent {
constructor(account: Address) {
const data = new BytesWriter(ADDRESS_BYTE_LENGTH);
data.writeAddress(account);
super('Paused', data);
}
}
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
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
class TokensMinted extends NetEvent { ... }
class LiquidityAdded extends NetEvent { ... }
class StakeWithdrawn extends NetEvent { ... }
// Less clear
class Action1 extends NetEvent { ... }
class Update extends NetEvent { ... }
```
### 3. Include Relevant Context
```typescript
// Good - includes all relevant data
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
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
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);
}
}
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)