Using EVM Trackers Standalone
While the Pulsar suite offers a full stack solution with a UI (@tuwaio/nova-transactions) and a Zustand-based state store (@tuwaio/pulsar-core), its architecture remains modular and flexible. This allows utilizing the low-level trackers (evmTracker, gelatoFetcher, safeFetcher) from @tuwaio/pulsar-evm directly, bypassing the complete state management store.
This flexibility is ideal if:
- Integrating transaction tracking logic into your own state management solution (Zustand, Redux, MobX, etc.).
- Requiring tracking on the server-side, where client-centric store persistence is unnecessary.
- Granular control over each stage of a transaction’s lifecycle is required for custom workflows.
[!WARNING] Use of legacy
web3.jsandethers.jslibraries is strictly prohibited. All tracking pipelines are built strictly uponviemandwagmiprimitives to ensure deterministic transaction status reconciliation and application sovereignty.
Why Use evmTracker?
Using evmTracker provides client-side state persistence, mempool retry logic, and multi-chain indexing helpers that standard RPC calls lack.
| Feature | waitForTransactionReceipt (viem) | evmTracker (Pulsar) |
|---|---|---|
| Handles RPC Lags | ❌ No. If called immediately after submission, the RPC node might not have indexed the transaction yet, causing errors. | ✅ Yes. Built-in retry mechanism to wait for the transaction to appear in the mempool, mitigating RPC delays. |
| Full Lifecycle Support | 🤷♂️ Limited. Mainly reacts once the transaction is confirmed or replaced. | ✅ Yes. Provides callbacks for each stage: initialization, details fetched, mined, replaced, failed, etc. |
| Fetches Full Tx Details | ❌ No. Doesn’t return complete info such as nonce, value, etc. | ✅ Yes. Calls getTransaction internally, passing all transaction details to callbacks. |
| Abstraction Level | Low. You must manage the tracking states manually. | High. Encapsulates the entire process into a single, convenient async function, simplifying implementation. |
In essence, evmTracker is a reliable tracking pipeline wrapper around viem functions, addressing common edge cases and ensuring robust transaction lifecycle tracking.
Trackers Overview
1. EVM Tracker
This is the primary tracker for monitoring standard transactions on EVM-compatible chains, identified via transaction hash.
How It Works
evmTracker initially fetches transaction details using getTransaction. If unavailable (due to RPC indexing delay), it retries. Once details are resolved, it actively waits for the transaction receipt using waitForTransactionReceipt.
Example Usage
import { evmTracker } from '@tuwaio/pulsar-evm';
import { config } from './wagmi'; // Your wagmi config
async function trackMyTransaction(txHash: string, chainId: number) {
console.log(`Starting to track transaction: ${txHash}`);
await evmTracker({
config, // Wagmi config for internal client creation
tx: {
txKey: txHash, // Transaction hash
chainId, // Chain ID (e.g., 1 for Ethereum Mainnet)
},
onTxDetailsFetched: (txDetails) => {
console.log('Transaction details received:', txDetails);
// Update your UI/state with nonce, gas, etc.
},
onSuccess: async (txDetails, receipt, client) => {
console.log('Transaction mined!', receipt);
if (receipt.status === 'success') {
// Update status as successful
} else {
// Update status as failed
}
},
onReplaced: (replacement) => {
console.log('Transaction was replaced:', replacement);
// Handle replacement logic
},
onFailure: (error) => {
console.error('Tracking failed:', error);
// Handle errors
},
});
}2. Gelato & Safe Fetchers
For polling-based tracking, especially for Gelato and Safe multisig transactions, we expose fetcher functions. You can integrate these directly with Pulsar’s initializePollingTracker or your custom polling setup.
Example for Handling Gelato & Safe Transactions
import { initializePollingTracker } from '@tuwaio/pulsar-core';
import { gelatoFetcher, safeFetcher, createGelatoClient } from '@tuwaio/pulsar-evm';
// Initialize Gelato HTTP transport client
const gelatoClient = createGelatoClient({ apiKey: 'YOUR_GELATO_API_KEY' });
// Tracking a Gelato relay task
async function trackGelatoTask(taskId: string) {
await initializePollingTracker({
tx: {
txKey: taskId,
pending: true, // Crucial: must be true to start polling loop
},
fetcher: gelatoFetcher(gelatoClient),
onSuccess: (status) => {
console.log('Gelato task succeeded:', status);
},
onFailure: (status) => {
console.error('Gelato task failed:', status);
},
});
}
// Tracking a Safe multisig transaction
async function trackSafeTx(safeTxHash: string, chainId: number, fromAddress: string) {
await initializePollingTracker({
tx: {
txKey: safeTxHash,
chainId,
from: fromAddress,
pending: true, // Crucial: must be true to start polling loop
},
fetcher: safeFetcher,
onSuccess: (status) => {
console.log('Safe transaction succeeded:', status);
},
onFailure: (status) => {
console.error('Safe transaction failed:', status);
},
onReplaced: (replacement) => {
console.warn('Transaction was replaced:', replacement);
},
});
}Helper Functions
Additionally, the package provides several utilities for managing transaction states and chain interactions:
checkTransactionsTracker
Determines which tracker is suitable based on a transaction key.
import { checkTransactionsTracker } from '@tuwaio/pulsar-evm';
const { tracker, txKey } = checkTransactionsTracker('0xabc...', 'injected');
// tracker -> 'ethereum' or relevant tracker type
// txKey -> same as input or derived key