Skip to Content
Solana Trackers Standalone

Solana Trackers Standalone

The Solana tracker of @tuwaio/pulsar-solana also works without the Pulsar store. Use it 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 check 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 tracker uses only @solana/kit RPC calls, so it runs in the browser and in Node.js.


Why solanaFetcher Instead of Manual Polling

SituationManual getSignatureStatuses pollingsolanaFetcher with initializePollingTracker
The RPC has not seen the signatureYou decide how long to wait.With lastValidBlockHeight, fails as “expired” as soon as the chain passes it; otherwise one hour after localTimestamp.
Transient RPC errorsYou write the retry logic.Errors are retried on the next tick; tracking gives up after maxRetries consecutive errors.
Tracking resumed laterRecent statuses only, unless you pass searchTransactionHistory.Always searches the transaction history, so transactions that finished while you were away are found.
Transaction detailsA separate getTransaction call.Fetches fee, recentBlockhash and instructions once and adds them to every reported status; a getTransaction error never hides the status or fails the transaction.
Progress and final statusYou compare commitment levels yourself.onIntervalTick with processed and confirmed, onSuccess at finalized, onFailure as soon as there is an error.

Tracking a Signature: solanaFetcher

Pass solanaFetcher to initializePollingTracker from @tuwaio/pulsar-core. It starts polling in the background and returns immediately:

import { OrbitAdapter, SOLANA_CHAIN_IDS } from '@tuwaio/orbit-core'; import { initializePollingTracker } from '@tuwaio/pulsar-core'; import { solanaFetcher } from '@tuwaio/pulsar-solana'; export function trackSignature(signature: string, lastValidBlockHeight?: number) { initializePollingTracker({ tx: { adapter: OrbitAdapter.SOLANA, txKey: signature, chainId: SOLANA_CHAIN_IDS.devnet, // the CAIP-2 chain ID; picks the public endpoint when rpcUrl is not set rpcUrl: 'https://api.devnet.solana.com', // prefer your own RPC provider localTimestamp: Math.floor(Date.now() / 1000), // when the transaction was sent, in seconds lastValidBlockHeight, // from the blockhash the transaction was signed with; optional (see below) pending: true, // polling starts only for pending transactions }, fetcher: solanaFetcher, pollingInterval: 1000, onIntervalTick: (status) => console.log(status.confirmationStatus, status.confirmations), // 'confirmed' within about a second onSuccess: (status) => console.log('Finalized in slot', status.slot, 'fee', status.fee), onFailure: (status) => { if (status?.expired) console.error('Expired: the blockhash is no longer valid, send the transaction again'); else console.error(status ? 'Failed on-chain' : 'Not finalized in time or too many RPC errors', status?.err); }, }); }

How each check works:

  1. getSignatureStatuses with searchTransactionHistory: true. If the signature is unknown and the transaction has a lastValidBlockHeight (or signAndSendSolanaTx sent it in this page, which records the height), the fetcher sends getBlockHeight (commitment confirmed): once the chain is past that height, it checks the signature once more and, still unknown, calls onFailure with expired: true, because the blockhash expired and the transaction can never land. Otherwise it waits for the next tick, until one hour after localTimestamp.
  2. getTransaction (commitment confirmed, maxSupportedTransactionVersion: 1, so legacy, version 0 and version 1 transactions are read) for the fee, blockhash and instructions, only until it has returned them once for this tx object: the details are cached in memory for the tracking run, or taken from tx if it already has them. A getTransaction error is not thrown: the check goes on with the status alone and asks again on the next tick.
  3. onIntervalTick with the status (and the details once known); then onFailure with the status if it has an err, right away, or onSuccess when it is finalized and the details are known. A finalized transaction whose details are still missing on its 10th check gets onSuccess without them, with a warning in the console. A transaction that is not finalized one hour after localTimestamp calls onFailure; a finalized one never does.

lastValidBlockHeight comes from the blockhash you signed with (getLatestBlockhash returns both). Durable-nonce transactions have no such limit: leave it out for them.

Errors of getSignatureStatuses and getBlockHeight are thrown to initializePollingTracker, which retries on the next tick and calls onFailure() without arguments after maxRetries (default 10) consecutive errors.


Helpers

signAndSendSolanaTx

Builds a version 0 transaction from one or more instructions, with the signer as fee payer and the latest blockhash, and has the signer sign and send it. It returns the base58 signature, ready for solanaFetcher:

import type { Instruction, TransactionSendingSigner } from '@solana/kit'; import type { SolanaClient } from '@tuwaio/orbit-solana'; import { signAndSendSolanaTx } from '@tuwaio/pulsar-solana'; export async function send(client: SolanaClient, signer: TransactionSendingSigner, instructions: Instruction[]) { const signature = await signAndSendSolanaTx({ client, signer, instruction: instructions }); console.log('Sent', signature); return signature; }

It rejects with the RPC or signer error, for example when the user rejects the transaction in the wallet.

checkSolanaChain

Compares two cluster identifiers exactly and throws SolanaChainMismatchError when they differ. The Solana adapter uses it to compare desiredChainID with the cluster saved for the wallet connection after turning both into cluster monikers with getCluster from @tuwaio/orbit-solana, so devnet, solana:devnet and the genesis-hash chain ID solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 match:

import { checkSolanaChain, SolanaChainMismatchError } from '@tuwaio/pulsar-solana'; export function isOnCluster(requiredCluster: string, walletCluster: string): boolean { try { checkSolanaChain(requiredCluster, walletCluster); return true; } catch (error) { if (error instanceof SolanaChainMismatchError) { console.warn(`Switch the wallet from ${error.currentChain} to ${error.requiredChain}.`); return false; } throw error; } }

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

Last updated on