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>
6.6 KiB
6.6 KiB
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
nullon timeout (no errors thrown) - Type safety: Full TypeScript support with proper event typing
Quick Start
DataHaven Events
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
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:
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:
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
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
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:
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
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:
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:
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:
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
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);
}