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
localStorageand 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
| Situation | Manual getSignatureStatuses polling | solanaFetcher with initializePollingTracker |
|---|---|---|
| The RPC has not seen the signature | You decide how long to wait. | Keeps polling; fails one hour after localTimestamp if the signature never appears (for example, the blockhash expired). |
| Transient RPC errors | You write the retry logic. | Errors are retried on the next tick; tracking gives up after maxRetries consecutive errors. |
| Tracking resumed later | Recent statuses only, unless you pass searchTransactionHistory. | Always searches the transaction history, so transactions that finished while you were away are found. |
| Transaction details | A separate getTransaction call. | Fetches fee, recentBlockhash and instructions once and adds them to every reported status. |
| Final status | You compare commitment levels yourself. | onSuccess at finalized, onFailure for an on-chain error; both stop polling. |
Tracking a Signature: solanaFetcher
Pass solanaFetcher to initializePollingTracker from @tuwaio/pulsar-core. It starts polling in the background and returns immediately:
import { OrbitAdapter } from '@tuwaio/orbit-core';
import { initializePollingTracker } from '@tuwaio/pulsar-core';
import { solanaFetcher } from '@tuwaio/pulsar-solana';
export function trackSignature(signature: string) {
initializePollingTracker({
tx: {
adapter: OrbitAdapter.SOLANA,
txKey: signature,
chainId: 'solana:devnet', // used to pick 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
pending: true, // polling starts only for pending transactions
},
fetcher: solanaFetcher,
pollingInterval: 2500,
onIntervalTick: (status) => console.log(status.confirmationStatus, status.confirmations),
onSuccess: (status) => console.log('Finalized in slot', status.slot, 'fee', status.fee),
onFailure: (status) =>
console.error(status ? 'Failed on-chain' : 'Not finalized in time or too many RPC errors', status?.err),
});
}How each check works:
getSignatureStatuseswithsearchTransactionHistory: true. If the signature is unknown, the fetcher reports nothing and waits for the next tick, until one hour afterlocalTimestamp.getTransaction(commitmentconfirmed) for the fee, blockhash and instructions, only until it has returned them once for thistxobject: the details are cached in memory for the tracking run, or taken fromtxif it already has them. Until they are known, nothing is reported.onIntervalTickwith the status; thenonFailurewith the status if it has anerr, oronSuccesswhen it isfinalized. A transaction that is not finalized one hour afterlocalTimestampcallsonFailure.
RPC errors 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 removing a solana: prefix from both, so devnet and solana:devnet 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.