mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Summary
This PR introduces comprehensive event waiting utilities for both
DataHaven (Substrate) and Ethereum chains, providing a unified
interface for handling blockchain events in E2E tests.
## What's New
- **Event Utilities** (`test/utils/events.ts`): New utilities for
waiting on blockchain events
- `waitForDataHavenEvent`: Type-safe event waiting for Substrate chain
events
- `waitForEthereumEvent`: Event waiting for Ethereum contract events
- Graceful timeout handling (returns null instead of throwing)
- Support for event filtering, callbacks, and custom timeouts
- **Documentation** (`test/docs/event-utilities-guide.md`):
Comprehensive guide covering usage examples for both DataHaven and
Ethereum.
## Test Plan
- [ ] New event utilities work as expected
- [ ] Event filtering works correctly for both chains
- [ ] Timeout handling behaves as documented
- [ ] Parallel event waiting with `Promise.all()` works
---------
Co-authored-by: Claude <noreply@anthropic.com>
298 lines
No EOL
6.6 KiB
Markdown
298 lines
No EOL
6.6 KiB
Markdown
# Event Utilities Usage Guide
|
|
|
|
This guide demonstrates how to use event utilities for waiting and handling blockchain events in DataHaven (Substrate) and Ethereum chains.
|
|
|
|
## Overview
|
|
|
|
The event utilities provide a unified, type-safe interface for handling blockchain events with:
|
|
|
|
- **Consistent API**: Similar patterns for both DataHaven and Ethereum
|
|
- **Composable design**: Use `Promise.all()` for parallel event waiting
|
|
- **Graceful timeouts**: Functions return `null` on timeout (no errors thrown)
|
|
- **Type safety**: Full TypeScript support with proper event typing
|
|
|
|
## Quick Start
|
|
|
|
### DataHaven Events
|
|
```typescript
|
|
import { waitForDataHavenEvent } from '@test/e2e-suite/utils/datahaven';
|
|
|
|
const result = await waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Balances",
|
|
event: "Transfer",
|
|
timeout: 10000
|
|
});
|
|
|
|
if (result.data) {
|
|
console.log(`Transfer: ${result.data.amount}`);
|
|
}
|
|
```
|
|
|
|
### Ethereum Events
|
|
```typescript
|
|
import { waitForEthereumEvent } from '@test/e2e-suite/utils/ethereum';
|
|
|
|
const result = await waitForEthereumEvent({
|
|
client: publicClient,
|
|
address: tokenAddress,
|
|
abi: erc20Abi,
|
|
eventName: "Transfer",
|
|
timeout: 10000
|
|
});
|
|
|
|
if (result.log) {
|
|
console.log(`Transfer: ${result.log.args.value}`);
|
|
}
|
|
```
|
|
|
|
## DataHaven Event Handling
|
|
|
|
### Transaction Submission (Direct Events)
|
|
|
|
When you submit your own transaction, you can get immediate access to all events:
|
|
|
|
```typescript
|
|
const result = await dhApi.tx.Balances
|
|
.transfer({ dest: recipient, value: amount })
|
|
.signAndSubmit(signer);
|
|
|
|
// result type: TxFinalized
|
|
if (result.ok) {
|
|
// Access all events from the transaction
|
|
const transfer = result.events.find(
|
|
e => e.pallet === "Balances" && e.name === "Transfer"
|
|
);
|
|
|
|
if (transfer) {
|
|
console.log(`Transferred: ${transfer.value.amount}`);
|
|
}
|
|
} else {
|
|
console.error(`Failed:`, result.dispatchError);
|
|
}
|
|
```
|
|
|
|
**Use this approach when:**
|
|
- ✅ You're submitting the transaction yourself
|
|
- ✅ You need events from that specific transaction
|
|
- ✅ You want synchronous access to results
|
|
|
|
### Waiting for External Events
|
|
|
|
Use `waitForDataHavenEvent` when monitoring for events from other sources:
|
|
|
|
```typescript
|
|
const result = await waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Balances",
|
|
event: "Transfer",
|
|
filter: (e) => e.to === myAddress,
|
|
timeout: 10000
|
|
});
|
|
|
|
if (result.data) {
|
|
console.log(`Received transfer: ${result.data.amount}`);
|
|
}
|
|
```
|
|
|
|
**Use this approach when:**
|
|
- ✅ Waiting for events from other transactions
|
|
- ✅ Monitoring cross-chain events
|
|
- ✅ Watching for external activity
|
|
- ✅ Implementing time-based conditions
|
|
|
|
#### With Filtering
|
|
```typescript
|
|
const result = await waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Balances",
|
|
event: "Transfer",
|
|
timeout: 10000,
|
|
// Only match transfers from specific sender with amount > 1000
|
|
filter: (event) => event.from === senderAddress && event.amount > 1000n
|
|
});
|
|
```
|
|
|
|
#### With Callbacks
|
|
```typescript
|
|
const result = await waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Staking",
|
|
event: "Rewarded",
|
|
timeout: 30000,
|
|
// Real-time processing as events are found
|
|
onEvent: (event) => {
|
|
console.log(`✅ Reward received: ${event.amount}`);
|
|
updateRewardsDisplay(event.amount);
|
|
// Callback doesn't affect the return value
|
|
}
|
|
});
|
|
```
|
|
|
|
### Multiple Events
|
|
|
|
Wait for multiple events in parallel:
|
|
|
|
```typescript
|
|
const [transfer, reward, slash] = await Promise.all([
|
|
waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Balances",
|
|
event: "Transfer",
|
|
timeout: 10000,
|
|
filter: (e) => e.to === myAddress
|
|
}),
|
|
waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Staking",
|
|
event: "Rewarded",
|
|
timeout: 5000 // Shorter timeout for optional event
|
|
}),
|
|
waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Staking",
|
|
event: "Slashed",
|
|
timeout: 5000
|
|
})
|
|
]);
|
|
|
|
// Handle results - some may be null (timeout)
|
|
if (!transfer.data) {
|
|
throw new Error("Expected transfer not received");
|
|
}
|
|
|
|
if (reward.data) {
|
|
console.log(`Received reward: ${reward.data.amount}`);
|
|
} else {
|
|
console.log("No rewards in this period (normal)");
|
|
}
|
|
|
|
if (slash.data) {
|
|
console.warn(`Got slashed: ${slash.data.amount}`);
|
|
}
|
|
```
|
|
|
|
## Ethereum Event Handling
|
|
|
|
### Basic Usage
|
|
|
|
```typescript
|
|
const result = await waitForEthereumEvent({
|
|
client: publicClient,
|
|
address: contractAddress,
|
|
abi: contractAbi,
|
|
eventName: "StateChanged",
|
|
timeout: 30000
|
|
});
|
|
|
|
if (result.log) {
|
|
console.log(`New state: ${result.log.args.newState}`);
|
|
console.log(`Block: ${result.log.blockNumber}`);
|
|
console.log(`Tx: ${result.log.transactionHash}`);
|
|
}
|
|
```
|
|
|
|
### With Argument Filtering
|
|
|
|
Filter events by their arguments:
|
|
|
|
```typescript
|
|
const result = await waitForEthereumEvent({
|
|
client: publicClient,
|
|
address: tokenAddress,
|
|
abi: erc20Abi,
|
|
eventName: "Transfer",
|
|
// Only match specific transfers
|
|
args: {
|
|
from: myAddress, // Must be FROM myAddress
|
|
to: recipientAddress // Must be TO recipientAddress
|
|
// Omit 'value' to match any amount
|
|
},
|
|
timeout: 30000
|
|
});
|
|
```
|
|
|
|
### With Callbacks
|
|
|
|
Process events in real-time:
|
|
|
|
```typescript
|
|
const result = await waitForEthereumEvent({
|
|
client: publicClient,
|
|
address: dexAddress,
|
|
abi: dexAbi,
|
|
eventName: "Swap",
|
|
args: {
|
|
tokenIn: wethAddress,
|
|
tokenOut: usdcAddress
|
|
},
|
|
onEvent: (log) => {
|
|
const { amountIn, amountOut } = log.args;
|
|
const rate = Number(amountOut) / Number(amountIn);
|
|
|
|
console.log(`Swap at rate: ${rate}`);
|
|
console.log(`Block: ${log.blockNumber}`);
|
|
|
|
// Update UI, send notifications, etc.
|
|
updatePriceDisplay(rate);
|
|
},
|
|
timeout: 60000
|
|
});
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Timeout Handling
|
|
|
|
Events that timeout return `null`, not an error:
|
|
|
|
```typescript
|
|
const result = await waitForDataHavenEvent({
|
|
api: dhApi,
|
|
pallet: "Staking",
|
|
event: "Rewarded",
|
|
timeout: 5000
|
|
});
|
|
|
|
if (!result.data) {
|
|
// Timeout - decide how to handle
|
|
console.log("No rewards within 5 seconds");
|
|
|
|
// Option 1: Continue (if event is optional)
|
|
// Option 2: Retry with longer timeout
|
|
// Option 3: Fail the test
|
|
throw new Error("Expected rewards not received");
|
|
}
|
|
|
|
// Safe to use result.data here
|
|
console.log(`Rewards: ${result.data.amount}`);
|
|
```
|
|
|
|
### Error vs Timeout
|
|
|
|
```typescript
|
|
try {
|
|
const result = await waitForEthereumEvent({
|
|
client: publicClient,
|
|
address: tokenAddress,
|
|
abi: erc20Abi,
|
|
eventName: "Transfer",
|
|
timeout: 10000
|
|
});
|
|
|
|
if (!result.log) {
|
|
// Timeout - not an error
|
|
console.log("No transfer within 10 seconds");
|
|
// Handle based on your needs
|
|
} else {
|
|
// Event found
|
|
processTransfer(result.log);
|
|
}
|
|
} catch (error) {
|
|
// Only actual errors reach here:
|
|
// - Network issues
|
|
// - Invalid parameters
|
|
// - Contract/API errors
|
|
console.error("Unexpected error:", error);
|
|
}
|
|
``` |