Tracking & Lifecycle
The TransactionTracker from sendTransaction — its change/confirmed/failed events and whenSubmitted/whenMined/whenConfirmed/whenFinalized promises — plus TransactionStatus, TransactionReceipt, TransactionStateChange and SimulationResult, and the timeout-vs-failed distinction.
sendTransaction() (and tx.send()) returns a TransactionTracker synchronously — the broadcast and confirmation polling run in the background. You follow the transaction either by awaiting the tracker's lifecycle promises or by subscribing to its events. The tracker also satisfies Settling<TransactionReceipt>, so it exposes settled, value, confirm(), and abort().
const tracker = evm.sendTransaction(tx);
const hash = await tracker.whenSubmitted(); // string — the broadcast id
const receipt = await tracker.whenConfirmed(); // TransactionReceiptEvents and promises
The lifecycle promises resolve once and cache their result, so awaiting one after the fact still resolves (or rejects with the real cause).
| Promise | Resolves with | When |
|---|---|---|
whenSubmitted() | Promise<string> | the transaction has a broadcast hash |
whenMined() | Promise<TransactionReceipt> | reaches mined (first inclusion) or later |
whenConfirmed(blocks?) | Promise<TransactionReceipt> | reaches confirmed |
whenFinalized() | Promise<TransactionReceipt> | reaches finalized |
Events are subscribed with on(event, listener), which returns an unsubscribe function:
const off = tracker.on("change", (c) => {
console.log(`${c.previous} → ${c.next}`);
});
tracker.on("confirmed", (receipt) => { /* success only */ });
tracker.on("failed", (err) => { /* err is a WativeError */ });
off(); // unsubscribe| Event | Listener argument | Fires |
|---|---|---|
"change" | (c: TransactionStateChange) => void | on every status transition |
"confirmed" | (r: TransactionReceipt) => void | only on a successful receipt |
"failed" | (e: WativeError) => void | on the first terminal failure |
"confirmed" fires only for a success. A reverted transaction does not fire "confirmed" with a success: false receipt — it fires "failed" (the receipt is still stored on tracker.receipt, and its revertReason is there if the node supplied one).
whenConfirmed(n) and whenFinalized()
Confirmation-depth tracking is not implemented. whenConfirmed() resolves at first inclusion (one block). Asking for more is refused rather than resolved early — because a caller who got an early resolve might treat the funds as reorg-safe:
await tracker.whenConfirmed(3);
// UNSUPPORTED_OP: whenConfirmed(3): confirmation-depth tracking is not supported;
// this resolves at first inclusion (1 block). Poll receipt.blockNumber against
// the chain head for deeper confirmation.Tracking ends at inclusion, so whenFinalized() does not always resolve. EVM receipt polling ends at confirmed; Solana polling ends at whichever of confirmed / finalized the node reports first. When tracking ends at confirmed, awaiting whenFinalized() rejects with an UNSUPPORTED_OP explaining that tracking stopped and the state will never be reached — it resolves only when the node genuinely reported finalized.
timeout vs failed — the distinction that matters
For a wallet library, the dangerous mistake is telling a caller a broadcast transaction is safe to resend when it might already be on the network. The two terminal statuses draw exactly that line.
| Status | Meaning | Safe to resend? |
|---|---|---|
failed | The send never left the process, or the endpoint read the transaction and refused it (RPC_REJECTED: nonce too low, insufficient funds, on-chain revert). | Yes — rebuild and send again. |
timeout | The broadcast got no usable answer, or no receipt arrived within the budget. The signed bytes may have been relayed. | No — look it up by its hash first. |
dropped | (Solana) the blockhash expired; the transaction can never be accepted. | Yes — safe to replace. |
aborted | The caller stopped observing via abort(). Asserts nothing about the chain. | — |
timeout (introduced in 2.4.0 as the word for broadcast, outcome unknown) is never entered from a poll being unable to reach the node — a transient poll failure keeps polling. Its unknown-outcome messages name the signed hash so you can look the transaction up before sending another. tracker.hash is left null on an unacknowledged broadcast; the transaction's own tx.hash still carries the signed id the message references.
TransactionStatus
type TransactionStatus =
| "draft" | "signed" | "submitted" | "pending"
| "mined" | "confirmed" | "finalized"
| "failed" | "dropped" | "aborted" | "timeout";The terminal (settled) statuses are confirmed, finalized, failed, dropped, timeout, and aborted. tracker.settled is true in exactly those.
TransactionReceipt
| Field | Type | Notes |
|---|---|---|
hash | string | transaction hash / signature |
vm | VmToken | "evm" / "svm" |
blockNumber | number | null | block / slot |
blockHash | string | null | EVM block hash |
index | number | null | transaction index in block |
success | boolean | whether the transaction succeeded |
gasUsed | bigint (opt) | EVM; dropped if the node reported it unparseably |
effectiveGasPrice | bigint (opt) | EVM |
fee | bigint (opt) | EVM; gasUsed × effectiveGasPrice when both are known |
logs | ReadonlyArray<unknown> (opt) | raw logs |
revertReason | string (opt) | only when the node volunteered one |
raw | unknown | the underlying node response |
TransactionStateChange
Each entry pushed onto tracker.history and passed to a "change" listener.
| Field | Type | Notes |
|---|---|---|
previous | TransactionStatus | status before the transition |
next | TransactionStatus | status after the transition |
at | number | epoch milliseconds |
reason | string (opt) | human-readable cause |
hash | string (opt) | hash, once known |
blockNumber | number (opt) | block / slot, once known |
confirmations | number (opt) | reserved; never populated (depth tracking is not implemented) |
SimulationResult
Returned by simulateTransaction() / tx.simulate().
| Field | Type | Notes |
|---|---|---|
success | boolean | whether the pre-flight would succeed |
error | string (opt) | failure message |
logs | ReadonlyArray<string> (opt) | execution logs |
returnData | string (opt) | EVM return data |
gasUsed | bigint (opt) | EVM estimated gas |
computeUnitsConsumed | number (opt) | Solana compute units |
raw | unknown | the underlying node response |