Skip to Content
EVM Trackers Standalone

EVM Trackers Standalone

The trackers of @tuwaio/pulsar-evm also work without the Pulsar store. Use them when you:

  • keep transactions in your own state (Redux, MobX, another Zustand store, a database);
  • track transactions on a server, where there is no localStorage and no wallet;
  • need a callback for each stage of the lifecycle instead of store updates.

Without the store nothing is persisted and nothing is resumed after a restart: you decide what to save and when to start tracking again. The trackers use only viem, @wagmi/core and HTTP APIs, so they run in the browser and in Node.js.


Why evmTracker Instead of waitForTransactionReceipt

SituationwaitForTransactionReceipt (viem)evmTracker (Pulsar)
The node has not indexed the txWaits for the receipt; the transaction details are not fetched.Retries getTransaction (10 times, 3 s apart by default) and passes the details (nonce, fees, to) to a callback.
Transient RPC errorsThe call rejects once its timeout or its own retries are exhausted.Retries waiting for the receipt up to 5 times with a growing delay, then calls onFailure.
Speed-up or cancel in the walletReported through onReplaced; the promise resolves with the new receipt.Calls onReplaced and stops, so the replaced transaction is never reported as successful.
Several confirmationsconfirmations option of the single call.Polls confirmations every 5 s and reports each count through onConfirmationsUpdate.

evmTracker calls onSuccess for every mined transaction, including reverted ones: check receipt.status.


Standard Transactions: evmTracker

evmTracker follows a transaction by its hash through the wagmi client of its chain, and resolves when tracking has finished:

import { evmTracker } from '@tuwaio/pulsar-evm'; import { createConfig, http } from '@wagmi/core'; import { sepolia } from 'viem/chains'; const wagmiConfig = createConfig({ chains: [sepolia], transports: { [sepolia.id]: http() } }); export async function trackTransaction(hash: `0x${string}`) { await evmTracker({ config: wagmiConfig, tx: { txKey: hash, chainId: sepolia.id, requiredConfirmations: 2 }, onTxDetailsFetched: (details) => console.log('Nonce', details.nonce), onConfirmationsUpdate: (confirmations) => console.log(`${confirmations}/2 confirmations`), onSuccess: async (_details, receipt) => { console.log(receipt.status === 'success' ? 'Confirmed' : 'Reverted', receipt.blockNumber); }, onReplaced: (replacement) => console.log(`Replaced (${replacement.reason}) by`, replacement.transaction.hash), onFailure: (error) => console.error('Tracking failed', error), }); }

The chain must be configured in the wagmi config. Tracking fails at once for the zero hash or an unknown chain.


ERC-4337 UserOperations: erc4337Tracker

erc4337Tracker polls eth_getUserOperationReceipt every 2 s until the UserOperation is included in a bundle, and gives up after 60 consecutive failed requests. It calls onSuccess with the hash of the bundle transaction; pass that hash to evmTracker if you also need block confirmations:

import { erc4337Tracker, evmTracker } from '@tuwaio/pulsar-evm'; import { type Config } from '@wagmi/core'; import { sepolia } from 'viem/chains'; declare const wagmiConfig: Config; export function trackUserOperation(userOpHash: `0x${string}`, pimlicoApiKey?: string) { erc4337Tracker({ // Uses bundlerUrl if set, else api.pimlico.io with the API key, else the rate-limited public Pimlico endpoint. tx: { txKey: userOpHash, chainId: sepolia.id, pimlicoApiKey, pending: true }, onSuccess: ({ hash }) => { if (!hash) return; void evmTracker({ config: wagmiConfig, tx: { txKey: hash, chainId: sepolia.id }, onTxDetailsFetched: () => {}, onSuccess: async (_details, receipt) => console.log('Bundle transaction', receipt.status), onReplaced: () => {}, onFailure: (error) => console.error(error), }); }, onFailure: (result) => console.error('UserOperation failed:', result?.reason ?? 'too many failed requests'), }); }

erc4337Tracker returns immediately; polling runs in the background. A reverted UserOperation calls onFailure with its revert reason.


Safe Multisig and Gelato: Polling Fetchers

Safe and Gelato are tracked by polling an API. Pass their fetcher to initializePollingTracker from @tuwaio/pulsar-core, which calls it every 5 s by default and gives up after 10 consecutive failed requests (it then calls onFailure() without arguments).

[!NOTE] Gelato relay is deprecated. Use ERC-4337 UserOperations with @tuwaio/orbit-evm and erc4337Tracker instead.

import { initializePollingTracker } from '@tuwaio/pulsar-core'; import { createGelatoClient, gelatoFetcher, safeFetcher } from '@tuwaio/pulsar-evm'; // Safe: txKey is the safeTxHash, from is the Safe address. export function trackSafeTransaction(safeTxHash: `0x${string}`, safeAddress: `0x${string}`, chainId: number) { initializePollingTracker({ tx: { txKey: safeTxHash, chainId, from: safeAddress, pending: true }, fetcher: safeFetcher, onSuccess: (status) => console.log('Executed in', status.transactionHash), onFailure: (status) => console.error(status ? 'Execution failed' : 'Not found or too many failed requests'), onReplaced: (executed) => console.warn('Another transaction with the same nonce was executed:', executed.safeTxHash), }); } // Gelato (deprecated): txKey is the task ID. export function trackGelatoTask(taskId: string, apiKey: string) { initializePollingTracker({ tx: { txKey: taskId, pending: true }, fetcher: gelatoFetcher(createGelatoClient({ apiKey })), onSuccess: (status) => console.log('Task succeeded', status), onFailure: (status) => console.error('Task failed', status), }); }
  • Safe: the fetcher calls the Safe Transaction Service of the chain (see SafeTransactionServiceUrls for the supported chains). A 404 response calls onFailure() without a response and stops. A transaction still pending one day after it was proposed calls onFailure with its status and stops.
  • Gelato: a task still pending one hour after it was created calls onFailure with its status and stops.

These fetchers stop with withoutRemoving: true, so a removeTxFromPool you pass to initializePollingTracker is called only when polling gives up after maxRetries consecutive errors.


Helpers

  • checkTransactionsTracker({ actionTxKey, connectorType, tracker }) returns the tracker Pulsar would use for a key: ERC4337 or Gelato only when requested, Safe for Safe connectors (evm:safe), Ethereum otherwise.
  • speedUpTxAction and cancelTxAction resend a pending EIP-1559 transaction with the same nonce and fees raised by 15%. They need the nonce and fee fields the tracker fetched; evmTracker then reports the original transaction as replaced.
  • selectEvmTxExplorerLink builds the block explorer (or Safe app) link of a transaction.
import { TransactionTracker } from '@tuwaio/pulsar-core'; import { checkTransactionsTracker } from '@tuwaio/pulsar-evm'; const { tracker, txKey } = checkTransactionsTracker({ actionTxKey: '0xabc123', connectorType: 'evm:metamask' }); console.log(tracker === TransactionTracker.Ethereum, txKey); // true '0xabc123'

The full list of exports is in the @tuwaio/pulsar-evm reference.

Last updated on