@tuwaio/pulsar-evm
@tuwaio/pulsar-evm is the EVM Layer 4 (L4) package of Pulsar, the transaction tracking project of TUWA Stage 2 (“State & Connection”, next to Satellite Connect). Built on @wagmi/core, viem and @tuwaio/orbit-evm, it provides the EVM adapter for @tuwaio/pulsar-core and trackers for standard transactions, ERC-4337 UserOperations, EIP-5792 call batches and Safe multisig transactions. It does not use ethers.js or web3.js.
🏛️ Core Capabilities
- Adapter:
pulsarEvmAdapter(wagmiConfig, appChains, options?)reads the wallet from the active wagmi connection, asks the wallet to switch todesiredChainIDbefore signing, picks the tracker, builds explorer links, and adds speed-up, cancel and retry actions for UI kits such as Nova Transactions. - Tracker routing: the key returned by your
actionFunctionis tracked as a Safe transaction when the connector is a Safe wallet, and as a standard transaction otherwise. ERC-4337 and EIP-5792 are never detected automatically: passtracker: TransactionTracker.ERC4337(orEIP5792) in the transaction params. - Standard transactions:
evmTrackerretriesgetTransactionwhile the node has not indexed the transaction yet, waits for the receipt in attempts, waits forrequiredConfirmationsand records the block timestamp. It sees a speed-up or cancel made in the wallet at any moment (ReplacedwithreplacedTxHashandreplacedReason): after every attempt without an answer it reads the sender’s nonce (readNonceSlot), which stays true when a node no longer returns the old hash, and after a reload it reads the sender and nonce saved on the transaction. A hash no public node shows is also asked of the wallet connected on its chain, through the wallet’s provider and with no prompt: a fee below what public nodes keep or a private mempool may leave only the wallet’s RPC holding it, and without its nonce no speed-up of it can be seen. - A speed-up continues in a new record: the old record stays as
Replaced, with the new hash inreplacedTxHash, and a new record under the new hash, linked byreplaces, goes on to its own confirmations andSuccessorFailed, with the callbacks ofexecuteTxAction. A cancel (replacedReason: 'cancelled') or another transaction in the nonce (replaced) is not the same operation, so it only ends the old record. A speed-up made in the wallet can be seen only once it is in a block. - A slow transaction is not a failed one: in the store, time alone never means
Failed. A transaction stays pending until it has a receipt or its nonce is spent, as in a wallet, and an RPC error only means asking again. Only a hash that no node has ever shown, with nothing saved to read instead, fails, 24 hours after it was sent. Alone, on a server,evmTrackergives up afterreceiptAttempts(6 by default, 435 s). - ERC-4337 UserOperations: a two-stage tracker polls
eth_getUserOperationReceipton your bundler (bundlerUrl, or Pimlico withpimlicoApiKey), then follows the bundle transaction on-chain like a standard transaction. After a reload it resumes at the stage it reached. - EIP-5792 call batches: for an
actionFunctionthat returns the batch ID ofwallet_sendCalls(sendCallsof@wagmi/core), a two-stage tracker asks the connected wallet for the batch status (wallet_getCallsStatus), then follows the transaction that executed the batch on-chain. The status comes from the wallet, so the wallet that sent the batch must stay connected until it is executed. - Safe multisig: polls the Safe Transaction Service until the
safeTxHashis executed, and reports it as replaced when another transaction with the same nonce was executed. While it waits, the transaction shows how many owners confirmed it (confirmations) out of the Safe threshold (requiredConfirmations), which Nova Transactions displays as “x / y”. It is checked every 5 s for the first hour after it was sent, then every 30 s. Collecting signatures can take days, so by default the tracker waits without a limit; setsafePendingTimeoutMsin the adapter options to give up after a time. A service that cannot be reached never ends tracking, and a 404 is final only after 10 minutes, as a new proposal is indexed with a delay. - Safe without wagmi:
@tuwaio/pulsar-evm/safeexports the Safe tracker alone (createSafeFetcher,safeFetcher,safeTrackerForStore,SafeTransactionServiceUrls). It loads neither@wagmi/corenor@tuwaio/orbit-evm, so apps built onviemalone can use it. - Speed up and cancel:
speedUpTxActionandcancelTxActionresend a pending EIP-1559 transaction with the same nonce, 15% above the fees last sent for it and at least at the market estimate, as a transaction stuck below the base fee stays stuck with 15% more. They work only from the account that sent the transaction. The original tracker then reports it asReplaced, withreplacedReasonrepricedorcancelled. - Standalone use: the trackers and fetchers work without the store, in your own state or on a server. See EVM Trackers Standalone .
Trackers write their results to the store with updateTxParams, and the onSuccess, onError and onReplaced callbacks of executeTxAction receive the updated transaction; the store’s onTxFinished and subscribeTxFinished run for every finished transaction, also after a reload. No tracker removes a transaction from the pool: when a tracker gives up (for example a Safe transaction still not executed when safePendingTimeoutMs runs out), the transaction is marked Failed with the reason in error and stays in the pool. An error thrown by your callback is logged and never changes the status of the transaction.
💾 Installation
pnpm add @tuwaio/pulsar-evm @tuwaio/pulsar-core @tuwaio/orbit-core @tuwaio/orbit-evm @wagmi/core viem zustand immer dayjs[!IMPORTANT]
@tuwaio/pulsar-core(>=0.12),@tuwaio/orbit-core(>=0.4),viem(2.x) anddayjs(1.x) are peer dependencies and must be installed alongside@tuwaio/pulsar-evm. The main entry also needs@tuwaio/orbit-evm(>=0.3) and@wagmi/core(3.x); they are optional peers because the@tuwaio/pulsar-evm/safeentry does not use them.zustandandimmerare the peer dependencies of@tuwaio/pulsar-core.
For the Safe tracker alone, on viem without wagmi:
pnpm add @tuwaio/pulsar-evm @tuwaio/pulsar-core @tuwaio/orbit-core viem dayjs🚀 Usage
Add the adapter to the store and pass tracker for UserOperations:
import { OrbitAdapter } from '@tuwaio/orbit-core';
import { createPimlicoSmartAccountClient } from '@tuwaio/orbit-evm';
import { createPulsarStore, type EvmTransaction, TransactionTracker } from '@tuwaio/pulsar-core';
import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm';
import { type Config } from '@wagmi/core';
import { mainnet, sepolia } from 'viem/chains';
declare const wagmiConfig: Config;
const pimlicoApiKey = process.env.NEXT_PUBLIC_PIMLICO_API_KEY;
export const pulsarStore = createPulsarStore<EvmTransaction>({
name: 'pulsar-transactions',
// Safe owners get three days to collect their signatures before the tracker gives up
adapter: pulsarEvmAdapter(wagmiConfig, [mainnet, sepolia], { safePendingTimeoutMs: 3 * 24 * 60 * 60 * 1000 }),
});
export async function pingWithSmartAccount() {
await pulsarStore.getState().executeTxAction({
actionFunction: async () => {
const { account, bundlerClient } = await createPimlicoSmartAccountClient({
chain: sepolia,
wagmiConfig,
apiKey: pimlicoApiKey,
});
// Returns the userOpHash, which becomes the txKey.
return bundlerClient.sendUserOperation({ account, calls: [{ to: account.address, value: 0n }] });
},
params: {
adapter: OrbitAdapter.EVM,
desiredChainID: sepolia.id,
type: 'ping',
title: 'Smart account ping',
tracker: TransactionTracker.ERC4337, // required for UserOperations
pimlicoApiKey, // or bundlerUrl; saved locally to resume tracking after a reload, never sent to onRemoteCreate
},
});
}The step-by-step React setup is in the React transaction tracking guide , and tracking without the store on EVM Trackers Standalone .
🗄️ Browser Storage
speedUpTxAction and cancelTxAction save the fees they last sent for a nonce in sessionStorage, under pulsar-evm:replacementFees:<chain>:<sender>:<nonce>, so the next replacement goes above them after a reload too. Only the two fees are saved, and they go away with the tab. The fees of a replacement sent by the wallet itself or from another tab are not known: if they are higher, the node refuses this one and the error says so. Everything else is saved by @tuwaio/pulsar-core.
🌐 External Services
The trackers send requests to these hosts. The transaction hash, userOpHash or safeTxHash (and for Safe, the Safe address) is sent to them:
| Tracker | Host | Purpose |
|---|---|---|
| Standard, stage 2 | The RPC transports of your wagmi config | getTransaction, receipts, confirmations and block timestamps; the sender’s nonce after every attempt without an answer, and once it is spent eth_getTransactionBySenderAndNonce, or its count at past blocks to find the one block to read; only a node without old state is read block by block, up to 1000 full blocks |
| ERC-4337 stage 1 | bundlerUrl, else api.pimlico.io (with pimlicoApiKey in the URL), else the rate-limited public public.pimlico.io | eth_getUserOperationReceipt |
| EIP-5792 stage 1 | The connected wallet (no host): wallet_getCallsStatus through the wagmi connector | Status of the call batch and the hash of the transaction that executed it |
| Safe | safe-transaction-<network>.safe.global (see SafeTransactionServiceUrls) | Status of the multisig transaction and of other transactions with its nonce |
Explorer links point to the block explorer configured in your viem chains, or to app.safe.global for Safe transactions; they are not requested by the package.
📚 API Reference
Every export, with signatures and types generated from the source, is documented at pulsar.docs.tuwa.io/packages/pulsar-evm .
📄 License
Licensed under the Apache-2.0 License. See the LICENSE file for details.
Classes
Type Aliases
- Eip5792FetcherTx
- Eip5792FetchResult
- Eip5792TrackerConfig
- Eip5792TrackerForStoreParams
- Erc4337FetcherTx
- Erc4337FetchResult
- Erc4337TrackerConfig
- Erc4337TrackerForStoreParams
- Erc4337UserOpReceipt
- EVMTrackerParams
- InitializeTrackerParams
- NonceSlot
- NonceSlotReading
- NonceSlotScan
- SafeTrackerOptions
- SafeTxStatusResponse
Variables
Functions
- cancelTxAction
- checkAndInitializeTrackerInStore
- checkTransactionsTracker
- createEip5792Fetcher
- createSafeFetcher
- eip5792Tracker
- eip5792TrackerForStore
- erc4337Fetcher
- erc4337Tracker
- erc4337TrackerForStore
- evmTracker
- evmTrackerForStore
- pulsarEvmAdapter
- readNonceSlot
- safePollingInterval
- safeTrackerForStore
- selectEvmTxExplorerLink
- speedUpTxAction