Transaction UX
Toasts, confirmations, retries, and error decoding for user-facing sends.
Recipe
type TxState = "idle" | "building" | "signing" | "sending" | "confirmed" | "failed";When to reach for this:
- Any button that triggers wallet approval.
- Reducing support tickets from opaque failures.
- Professional polish on mainnet launches.
Working Example
"use client";
import { useState } from "react";
export function useTxToast() {
const [state, setState] = useState<TxState>("idle");
const [signature, setSignature] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
async function run(sendFn: () => Promise<string>) {
try {
setError(null);
setState("signing");
const sig = await sendFn();
setSignature(sig);
setState("confirmed");
return sig;
} catch (e) {
setError(e instanceof Error ? e.message : "Transaction failed");
setState("failed");
throw e;
}
}
return { state, signature, error, run };
}What this demonstrates:
- Explicit state machine for UI spinners.
- Capture signature for explorer link.
- Surface wallet rejection vs program error differently in UI layer.
Deep Dive
How It Works
- Simulate before sign to map program logs to copy.
- Show explorer link on confirmed.
- Retry with fresh blockhash on expiry errors.
- Disable duplicate submits while in-flight.
Gotchas
- Generic "failed" toast - users retry blindly. Fix: decode simulation logs.
- No pending state - double clicks. Fix: disable button.
- Assuming instant finalized - premature success. Fix: label commitment level.
- Missing explorer link - support friction. Fix: cluster-aware URL template.
- Infinite retry loop - same program error. Fix: cap retries; show fix guidance.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Email on confirmed | High-value ops | Consumer game txs |
| Silent background send | Analytics | User-initiated transfers |
FAQs
App Router?
Providers in client layout.tsx; pages mix server + client components.
Secrets?
Server env only for keypairs; NEXT_PUBLIC_ for RPC URLs.
kit 7.0.0?
All examples use @solana/kit, not web3.js v1.
Wallet?
Signing only in client components.
Caching?
Revalidate on-chain reads carefully - chain is source of truth.
Actions?
HTTPS endpoints returning Solana Actions JSON.
Mobile?
Separate React Native app with MWA.
Testing?
Surfpool for integration; mock RPC in unit tests.
Mainnet?
Separate env files and program IDs.
Errors?
Decode simulation logs for user-facing messages.
Related
- Client-Side Signing - send pipeline
- Signing & Sending - kit confirm
- Debugging Failed Transactions - ops
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.