TxFix is the first diagnostic-first rescue tool for stuck Bitcoin transactions. Every existing tool (accelerators, wallet bump buttons) skips diagnosis and jumps to the most expensive fix. TxFix tells you what's actually wrong for free, then walks you to the cheapest fix in 3 clicks.
V1 ships completely free — no payment gating, no monetization. The app is a static site deployed to GitHub Pages — no backend, no database, pure frontend + public Bitcoin APIs.
The entire app is client-side JavaScript. There are zero server dependencies:
- Data: fetched at runtime from public mempool.space + Esplora REST APIs (CORS-enabled)
- Bitcoin logic: bitcoinjs-lib runs 100% in the browser (pure JS, no native modules)
- Build output:
next buildwithoutput: 'export'produces a plainout/folder of HTML/JS/CSS - No API routes, no SSR, no middleware — just static files served by GitHub Pages
The only technical consideration is browser polyfills for bitcoinjs-lib (it uses Node.js Buffer). TxFix handles this with explicit imports (import { Buffer } from 'buffer') rather than webpack global polyfills, ensuring compatibility with both Turbopack (dev) and webpack (build).
Single-page app. One route (/), URL query params drive state (?tx=TXID&network=mainnet). All data fetched client-side from public APIs.
User pastes TXID → Client fetches from mempool.space + Esplora APIs
→ Diagnostic engine runs checks (RBF, CPFP, fees, position, wait)
→ Animated step-by-step reveal → Verdict card (STUCK/SLOW/FINE)
→ "Fix it" → PSBT constructed client-side (bitcoinjs-lib)
→ Delivered as BBQr QR / .psbt file / raw hex
→ User signs in own wallet → Broadcast → Live tracker → Rescue receipt
No keys ever touch TxFix. PSBTs are unsigned templates the user signs in their own wallet.
| Layer | Technology |
|---|---|
| Framework | Next.js 16.1.6 (static export, App Router) |
| Language | TypeScript 5, React 19 |
| Styling | Tailwind CSS 4 (dark-only, CSS variables) |
| Animation | Motion (React 19 compatible successor to Framer Motion) |
| Bitcoin | bitcoinjs-lib v7 + @bitcoinerlab/secp256k1 + buffer polyfill |
| QR Codes | BBQr (animated QR for PSBTs) + qrcode.react (simple QR) |
| APIs | mempool.space REST + Blockstream Esplora (public, no auth) |
| Deploy | GitHub Pages via GitHub Actions |
Networks supported: mainnet, testnet4, signet (all mempool.space-supported Bitcoin networks)
src/
├── app/
│ ├── layout.tsx # Root layout, fonts, metadata, NetworkProvider
│ ├── page.tsx # Single page: view state machine
│ ├── globals.css # Dark theme design system
│ └── favicon.ico
│
├── components/
│ ├── ui/ # Reusable atoms
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ │ ├── Badge.tsx
│ │ ├── Spinner.tsx
│ │ ├── CopyButton.tsx
│ │ └── MonoText.tsx # Monospace wrapper for TXIDs, fees
│ │
│ ├── Header.tsx # Logo + network selector
│ ├── Footer.tsx # Minimal: GitHub link, version
│ ├── NetworkSelector.tsx # mainnet / testnet4 / signet dropdown
│ ├── TxInput.tsx # Hero TXID input (paste-and-go)
│ ├── DiagnosticSequence.tsx # Animated terminal-style step list
│ ├── DiagnosticStep.tsx # Single check line (icon + label + detail)
│ ├── VerdictCard.tsx # STUCK/SLOW/FINE + recommendations
│ ├── WalletSelector.tsx # Wallet picker with search + filtering
│ ├── WalletGuide.tsx # Step-by-step wallet-specific instructions
│ ├── FixFlow.tsx # Orchestrates PSBT build + delivery
│ ├── CpfpAddressInput.tsx # Destination address input for CPFP
│ ├── PsbtDelivery.tsx # Tabbed: QR / Download / Hex
│ ├── BbqrAnimatedQr.tsx # BBQr animated QR renderer
│ ├── PsbtFileDownload.tsx # .psbt file download button
│ ├── PsbtHexDisplay.tsx # Raw hex + copy
│ ├── BroadcastInput.tsx # Paste signed tx hex → broadcast
│ ├── LiveTracker.tsx # Real-time confirmation monitor
│ ├── RescueReceipt.tsx # Shareable rescue summary card
│ └── AcceleratorFallback.tsx # Links when RBF/CPFP impossible
│
├── lib/
│ ├── api/
│ │ ├── mempool.ts # mempool.space client (factory with baseUrl)
│ │ ├── esplora.ts # Blockstream Esplora client
│ │ └── types.ts # API response types (MempoolTransaction, etc.)
│ │
│ ├── diagnosis/
│ │ ├── engine.ts # AsyncGenerator pipeline (yields CheckResults)
│ │ ├── types.ts # DiagnosisState, Verdict, CheckResult, etc.
│ │ └── checks/
│ │ ├── confirmationStatus.ts
│ │ ├── rbf.ts # Sequence number analysis
│ │ ├── cpfp.ts # Unspent output detection
│ │ ├── feeAnalysis.ts # Fee rate vs mempool target
│ │ ├── mempoolPosition.ts # Position in mempool queue
│ │ └── waitEstimate.ts # Projected confirmation time
│ │
│ ├── psbt/
│ │ ├── rbfBuilder.ts # RBF replacement PSBT construction
│ │ ├── cpfpBuilder.ts # CPFP child PSBT construction
│ │ └── helpers.ts # Change detection, dust limits, script utils
│ │
│ ├── bitcoin/
│ │ ├── networks.ts # Network configs (URLs, bitcoinjs networks)
│ │ ├── constants.ts # DUST_LIMIT, MAX_SEQUENCE, etc.
│ │ ├── fees.ts # Fee calculation helpers (RBF cost, CPFP cost)
│ │ └── format.ts # Sats formatting, truncate TXID, etc.
│ │
│ ├── wallets/
│ │ ├── types.ts # WalletInfo, ResolvedGuide, GuideStep types
│ │ ├── data.ts # Static wallet data (11+ wallets)
│ │ └── resolveGuide.ts # Guide resolution (native/psbt/unsupported)
│ │
│ └── errors.ts # TxFixError class + ErrorCode enum
│
├── hooks/
│ ├── useDiagnosis.ts # Main hook: fetch → diagnose → result
│ ├── useUrlState.ts # Reads/writes ?tx= and ?network= URL params
│ ├── usePsbtBuilder.ts # On-demand PSBT construction (dynamic import)
│ ├── useLiveTracker.ts # Polls tx status for confirmation
│ └── useBroadcast.ts # POST signed tx to mempool.space
│
├── context/
│ └── NetworkContext.tsx # React context for network selection
│
public/
├── .nojekyll # Required for GitHub Pages
├── CNAME # txfix.it (when domain is ready)
└── (favicons, OG image)
.github/
└── workflows/
└── deploy.yml # Build + deploy to GitHub Pages
Config, theme, layout, network infrastructure
Modify:
next.config.ts— Addoutput: 'export',trailingSlash: true,images: { unoptimized: true }. No webpack polyfills needed — TxFix uses explicitbufferimports instead of global polyfills, which works with both Turbopack (dev) and webpack (build).src/app/globals.css— Dark-only theme with CSS custom properties:--background: #0a0a0a,--card-bg: #18181b,--card-border: #27272a,--bitcoin: #F7931A,--success: #22c55e,--warning: #eab308,--danger: #ef4444,--muted: #71717asrc/app/layout.tsx— Update metadata (title: "TxFix — Fix Stuck Bitcoin Transactions"), wrap children in<Suspense>+<NetworkProvider>, keep Geist + Geist Mono fontspackage.json— Add all dependencies up-front
Create:
src/lib/bitcoin/polyfills.ts— Single file that sets upglobalThis.Bufferfrom thebufferpackage. Imported once at the top of any entry point that uses bitcoinjs-lib (lazy-loaded with dynamic import, so it only runs when needed).src/context/NetworkContext.tsx— Stores network selection, provides API base URLssrc/hooks/useUrlState.ts— Reads/writes?tx=and?network=params (must be inside<Suspense>)src/lib/bitcoin/networks.ts— Config map:{ mainnet: { mempoolBaseUrl, esploraBaseUrl, bitcoinjsNetwork, explorerUrl }, testnet4: {...}, signet: {...} }src/lib/bitcoin/constants.ts—MAX_SEQUENCE = 0xFFFFFFFF,RBF_MAX_SEQUENCE = 0xFFFFFFFD, dust limits by script typesrc/lib/bitcoin/format.ts—formatSats(),truncateTxid(),feeRateDisplay()src/lib/errors.ts—TxFixErrorclass with error codes and user-friendly messagessrc/components/ui/*— Button, Card, Badge, Spinner, CopyButton, MonoTextsrc/components/Header.tsx— Logo text ("txfix") + NetworkSelectorsrc/components/Footer.tsx— Minimal footersrc/components/NetworkSelector.tsx— Dropdown with colored dot indicatorspublic/.nojekyll— Empty file
Install:
pnpm add motion bitcoinjs-lib ecpair @bitcoinerlab/secp256k1 buffer bbqr qrcode.reactNote: No crypto-browserify or stream-browserify needed — bitcoinjs-lib v7 with @bitcoinerlab/secp256k1 handles crypto internally, and TxFix uses explicit Buffer imports instead of webpack shims.
Verify: pnpm build produces out/ directory successfully. Dev server shows dark themed page with header + network selector.
Type-safe clients for mempool.space and Esplora
Create:
-
src/lib/api/types.ts— Types matching actual API responses:MempoolTransaction(txid, version, locktime, size, weight, fee, vin[], vout[], status)MempoolVin(txid, vout, prevout: {scriptpubkey, scriptpubkey_type, scriptpubkey_address, value}, sequence, witness, scriptsig)MempoolVout(scriptpubkey, scriptpubkey_type, scriptpubkey_address, value)TxStatus(confirmed, block_height, block_hash, block_time)RecommendedFees(fastestFee, halfHourFee, hourFee, economyFee, minimumFee)MempoolBlock(blockVSize, nTx, feeRange[])MempoolInfo(count, vsize, total_fee, fee_histogram)OutspendStatus(spent, txid, vin, status)Utxo(txid, vout, value, status)
-
src/lib/api/mempool.ts— Factory functioncreateMempoolClient(baseUrl)returning:getTransaction(txid)— GET/tx/{txid}getTxStatus(txid)— GET/tx/{txid}/statusgetTxHex(txid)— GET/tx/{txid}/hexgetTxOutspends(txid)— GET/tx/{txid}/outspendsgetRecommendedFees()— GET/v1/fees/recommendedgetMempoolBlocks()— GET/v1/fees/mempool-blocksgetMempoolInfo()— GET/mempoolbroadcastTx(hex)— POST/txgetPrice()— GET/v1/prices(for USD estimates)- Error handling: 404→TX_NOT_FOUND, 429→RATE_LIMITED, 5xx→API_UNAVAILABLE, network→NETWORK_ERROR
- Retry: 3 attempts with exponential backoff (1s, 2s, 4s) for 5xx/network errors only
-
src/lib/api/esplora.ts— Factory functioncreateEsploraClient(baseUrl)returning:getAddressUtxos(address)— GET/address/{address}/utxo- (Other endpoints overlap with mempool.space, use mempool as primary)
Verify: Create a temp test in page.tsx that fetches a known mainnet TXID and logs the parsed response.
The intellectual core — analyzes transactions and produces verdicts
Create:
-
src/lib/diagnosis/types.ts:CheckStatus:'running' | 'pass' | 'warn' | 'fail' | 'info'CheckResult:{ id, label, detail, status, icon: '✓'|'⚠'|'✗'|'○'|'⟳' }VerdictSeverity:'STUCK' | 'SLOW' | 'FINE' | 'CONFIRMED'Verdict:{ severity, headline, explanation, recommendations[], canRbf, canCpfp, showAcceleratorFallback }Recommendation:{ method: 'RBF'|'CPFP'|'WAIT'|'ACCELERATOR', label, isPrimary, costSats, costUsd, estimatedTime, targetFeeRate }CpfpCandidate:{ outputIndex, address, value, scriptPubKey, scriptType }
-
src/lib/diagnosis/engine.ts— AsyncGenerator pipeline that yieldsCheckResultone at a time (this drives the animated UI):- Fetch all data in parallel: tx, txHex, fees, mempoolBlocks, outspends
yieldconfirmation status check (already confirmed? → CONFIRMED verdict, done)yieldRBF signaling check (sequence < 0xFFFFFFFE? + note full-RBF era since Core 28)yieldCPFP feasibility (unspent outputs above dust limit?)yieldfee adequacy (current rate vs next-block minimum)yieldmempool position (how many vbytes ahead at higher fees)yieldwait estimate (projected blocks to confirmation)returnfinalVerdictwith recommendations
-
src/lib/diagnosis/checks/confirmationStatus.ts— Checktx.status.confirmed -
src/lib/diagnosis/checks/rbf.ts— Check allvin[].sequencevalues. RBF signaled if any < 0xFFFFFFFE. Note: full-RBF relay is default since Bitcoin Core 28+, so replacement may work even without signal. -
src/lib/diagnosis/checks/cpfp.ts— Cross-referenceoutspends[]to find unspent outputs withscriptpubkey_addressabove dust limit. These are CPFP candidates. -
src/lib/diagnosis/checks/feeAnalysis.ts— Calculatetx.fee / (tx.weight / 4)= effective fee rate in sat/vB. Compare againstrecommendedFees.fastestFeeandmempoolBlocks[0].feeRange[0]. -
src/lib/diagnosis/checks/mempoolPosition.ts— WalkmempoolInfo.fee_histogram(descending by fee rate), sum vsizes of entries with higher fee rate to estimate queue position. -
src/lib/diagnosis/checks/waitEstimate.ts— WalkmempoolBlocks[]to find which projected block this tx would land in. Each block ≈ 10 minutes. -
src/lib/bitcoin/fees.ts:calculateRbfCost(tx, targetFeeRate)→{ additionalFeeSats, newTotalFee }(new fee = ceil(vsize × targetRate), delta = new - old)calculateCpfpCost(parentTx, candidate, targetFeeRate)→{ childFeeSats, effectiveChildFeeRate }(child must pay enough to bring parent+child package to target rate)
-
src/hooks/useDiagnosis.ts— Consumes the async generator, updating React state as each step yields:- State:
{ phase: 'idle'|'fetching'|'diagnosing'|'complete'|'error', steps: CheckResult[], verdict: Verdict|null, txData, rawTxHex, error } - On new txid: reset state, create API clients from NetworkContext, run
engine.tsgenerator - Each yielded
CheckResultappends tosteps[](triggers re-render → animation) - Generator return value becomes
verdict
- State:
Verify: Paste known stuck/confirmed/slow TXIDs on testnet4, inspect diagnosis results in console.
The user-facing diagnosis experience
Create:
-
src/components/TxInput.tsx—'use client'- Full-viewport centered when no TXID (hero layout)
- Large monospace input, placeholder: "Paste a transaction ID..."
- Validates 64-char hex on submit/paste
- Auto-detects paste events → immediate submit ("paste and go")
- Bitcoin orange glow on focus
- Inline error for invalid TXIDs
-
src/components/DiagnosticStep.tsx— Single line:[icon] [label] — [detail]- Icon color: green (pass), yellow (warn), red (fail), gray (info), spinning (running)
- Motion:
initial={{ opacity: 0, x: -10 }}→animate={{ opacity: 1, x: 0 }}
-
src/components/DiagnosticSequence.tsx—'use client'- Dark terminal-style container with monospace font
- Maps
steps[]to<DiagnosticStep>components - Each step animates in as it's added (staggered by the generator timing)
- Spinner at bottom while diagnosis is running
-
src/components/VerdictCard.tsx—'use client'- Colored top border: red=STUCK, yellow=SLOW, green=FINE, blue=CONFIRMED
- Large severity badge
- Headline in large text (e.g., "~14 hours at current fee rate")
- Explanation in muted text
- Recommendation rows: method badge, cost in sats (~$USD), estimated time, "Fix it" button
- Primary recommendation visually emphasized (Bitcoin orange button)
- Motion: card slides up with fade
-
src/components/CostComparison.tsx— Simple visual: "RBF: ~$2.70 vs Accelerator: ~$40 — save $37.30" -
src/components/AcceleratorFallback.tsx— Shown when no RBF/CPFP possible. Links to mempool.space/tx/{txid} and ViaBTC. Explains why.
Modify:
src/app/page.tsx— Wire up the view state machine:- No txid →
<TxInput>hero - Diagnosing →
<DiagnosticSequence>(with<TxInput>collapsed at top) - Complete →
<DiagnosticSequence>+<VerdictCard> - Error → Error display with retry
- No txid →
Verify: Full flow: paste TXID → animated diagnosis → verdict card. Test with mainnet, testnet4, signet.
Client-side transaction building with bitcoinjs-lib
Create:
-
src/lib/psbt/helpers.ts:isSegWitInput(vin)— checks scriptpubkey_type ∈ {v0_p2wpkh, v0_p2wsh, v1_p2tr}getDustLimit(scriptType)— 294 for witness types, 546 for legacyidentifyChangeOutput(tx)— heuristic: match input address type, prefer non-round amounts, fallback to last matching output. If ambiguous, return all candidates and let UI ask user.extractRedeemScript(scriptsig)— for P2SH-wrapped SegWit inputs
-
src/lib/psbt/rbfBuilder.ts—buildRbfPsbt({ originalTx, originalTxHex, targetFeeRate, network }):- Calculate new fee:
ceil(vsize × targetFeeRate) - Identify change output (reduce its value by fee delta)
- Verify change output stays above dust after reduction
- Add all original inputs with
sequence ≤ 0xFFFFFFFD(ensure RBF signal) - For SegWit inputs: set
witnessUtxo = { script, value }from prevout - For legacy inputs: set
nonWitnessUtxo(raw prev tx hex — may need extra API call) - For P2SH-wrapped SegWit: add
redeemScript - Add all original outputs with adjusted change value
- Return
{ psbt, psbtBase64, psbtHex, newFee }
- Calculate new fee:
-
src/lib/psbt/cpfpBuilder.ts—buildCpfpPsbt({ parentTx, candidate, targetFeeRate, destinationAddress, network }):- Calculate required child fee (package fee rate math: parentVsize + childVsize × target - parentFee)
- Create 1-input-1-output tx: spend candidate UTXO → destinationAddress
- Verify output value (candidate.value - childFee) > dust limit
- Set
witnessUtxofrom candidate's scriptPubKey and value - Return
{ psbt, psbtBase64, psbtHex, childFee } - Default destinationAddress = same as candidate address (self-spend, simplest UX)
-
src/hooks/usePsbtBuilder.ts— Dynamic imports bitcoinjs-lib + builders (they're large, only load when user clicks "Fix it"):const build = async (method, params) => { const mod = await import(method === 'RBF' ? '@/lib/psbt/rbfBuilder' : '@/lib/psbt/cpfpBuilder'); return mod.build(params); };
Verify: Build PSBT for a testnet4 transaction, import the .psbt file into Sparrow Wallet, verify it parses correctly with correct inputs/outputs/fee.
QR codes, file download, hex display
Create:
-
src/components/BbqrAnimatedQr.tsx—'use client'- Uses
bbqr.splitQRs(data, 'P', { encoding: 'Z' })to split PSBT into frames - Cycles through frames at 4 FPS using
setInterval - If PSBT fits in single QR (< ~2953 bytes), show static QR via
qrcode.reactinstead - Dark QR (white on #18181b background)
- Frame counter: "3 / 12"
- Uses
-
src/components/PsbtFileDownload.tsx— Button that creates Blob from base64 PSBT, triggers download astxfix-rescue-{timestamp}.psbt -
src/components/PsbtHexDisplay.tsx— Scrollable monospace container with full hex + CopyButton -
src/components/PsbtDelivery.tsx—'use client'- Three tabs: "Scan QR" / "Download File" / "Copy Hex"
- Instructions per tab:
- QR: "Scan with your hardware wallet or Sparrow Wallet"
- File: "Import this .psbt file into Sparrow, Electrum, or any PSBT-compatible wallet"
- Hex: "Paste this hex into your wallet's PSBT import"
- Shows fee summary: "New fee: X sats (Y sat/vB) — Additional cost: Z sats"
-
src/components/CpfpAddressInput.tsx— For CPFP: input for destination address, pre-filled with the candidate output address (self-spend default), with option to change -
src/components/FixFlow.tsx— Orchestrates: choose method → (CPFP? get address) → build PSBT → show delivery
Verify: Generate all three delivery formats, import each into Sparrow on testnet4.
Sign → Broadcast → Watch confirmation
Create:
-
src/components/BroadcastInput.tsx—'use client'- Text area: "Paste your signed transaction hex here"
- "Broadcast" button → calls mempool.space
POST /tx - Shows success (new TXID) or error (rejection reason from node)
- Alternative: "Already broadcast? Paste the replacement TXID" input
-
src/hooks/useBroadcast.ts— CallsmempoolClient.broadcastTx(hex), returns txid or error -
src/hooks/useLiveTracker.ts— PollsgetTxStatus(txid)every 10s:- States:
pending → mempool → confirmed - Stops polling on confirmation
- Returns
{ status, blockHeight, confirmations }
- States:
-
src/components/LiveTracker.tsx—'use client'- Progress steps: "Broadcasting..." → "In mempool" → "Confirmed"
- Shows link to mempool.space explorer for the tx
- Animated state transitions (Motion)
- Subtle celebration on confirmation (green checkmark animation)
Verify: Broadcast a signed testnet4 transaction, watch it confirm in the tracker.
Shareable proof of rescue
Create:
src/components/RescueReceipt.tsx—'use client'- Dark card with Bitcoin orange accent
- Shows: original TXID (truncated), method used (RBF/CPFP), fee paid, savings vs accelerator
- Headline: "Rescued by TxFix — saved $X.XX vs. accelerator"
- "Copy Summary" button (copies formatted text to clipboard)
- Screenshot-friendly layout (fixed dimensions, high contrast)
- Link back to txfix.it
Verify: Complete full flow end-to-end on testnet4: paste TXID → diagnosis → fix → broadcast → confirm → receipt.
Error handling, mobile, accessibility, deployment
Tasks:
- Error handling audit — Every API call has user-friendly error messages, retry buttons
- Mobile responsiveness — Test all views at 375px width. TxInput, VerdictCard, PsbtDelivery must be fully usable on mobile
- Loading states — Skeleton/shimmer where appropriate, spinners for async ops
- Accessibility — Keyboard navigation, focus management between views, aria-labels on icons, screen reader text for status icons
- SEO/Meta — OG image, description, favicon (Bitcoin-themed).
robots.txt,sitemap.xmlin/public/ - Performance — Dynamic import bitcoinjs-lib + bbqr (only loaded on "Fix it" click). Lazy-load heavy components.
- GitHub Actions deploy —
.github/workflows/deploy.yml: checkout → pnpm install → pnpm build → uploadout/→ deploy pages - Static export validation —
pnpm build && npx serve outlocally, test all flows
Modify:
next.config.ts— ConditionalbasePath/assetPrefixfor GitHub Pages subdomain vs custom domain. Note:next.config.tsstays simple (no webpack overrides needed thanks to explicit polyfill imports).public/CNAME— Add when custom domain (txfix.it) is configured
idle → (paste TXID) → diagnosing → complete → (click Fix) → wallet-select →
(choose wallet) → wallet-guide → (done) → tracking → confirmed → receipt
Views: input | diagnosis | verdict | wallet-select | wallet-guide | tracker | receipt
Driven by useDiagnosis phase + local UI state. useUrlState syncs TXID and network with URL for shareability.
Critical: useSearchParams() requires <Suspense> boundary in static export, or build fails.
- BIP125 rules: replacement must pay strictly higher absolute fee + sufficient fee rate
- TxFix keeps same inputs, same outputs, only reduces change output value
- Change output identification: heuristic (match input address type → non-round amount → last output)
- If change too small after fee increase → error, suggest CPFP instead
- Full-RBF era (Bitcoin Core 28+): replacement works even without explicit RBF signal in most cases
- Spend an unspent output from the stuck tx with a high-fee child
- Child fee must bring package (parent + child) to target fee rate
- Formula:
childFee = ceil((parentVsize + childVsize) × targetRate) - parentFee - Default: self-spend (send back to same address), user can change destination
- 1-input-1-output child tx, estimated vsizes by script type (110 for P2WPKH, 111 for P2TR)
bitcoinjs-lib needs Node.js Buffer in the browser. Instead of fragile webpack resolve.fallback polyfills (which break with Turbopack in dev), TxFix uses explicit imports:
// src/lib/bitcoin/polyfills.ts — imported once before any bitcoinjs-lib usage
import { Buffer } from 'buffer';
if (typeof globalThis.Buffer === 'undefined') {
globalThis.Buffer = Buffer;
}This file is dynamically imported alongside bitcoinjs-lib (only when user clicks "Fix it"), keeping the initial bundle clean. Uses @bitcoinerlab/secp256k1 (pure JS) instead of native/WASM secp256k1. No crypto-browserify or stream-browserify needed — bitcoinjs-lib v7 handles crypto internally.
const NETWORKS = {
mainnet: { mempool: 'https://mempool.space/api', esplora: 'https://blockstream.info/api', explorer: 'https://mempool.space' },
testnet4: { mempool: 'https://mempool.space/testnet4/api', esplora: 'https://mempool.space/testnet4/api', explorer: 'https://mempool.space/testnet4' },
signet: { mempool: 'https://mempool.space/signet/api', esplora: 'https://mempool.space/signet/api', explorer: 'https://mempool.space/signet' },
}| Token | Value | Usage |
|---|---|---|
--background |
#0a0a0a |
Page background |
--card-bg |
#18181b |
Cards, panels |
--card-border |
#27272a |
Borders, dividers |
--foreground |
#ededed |
Primary text |
--muted |
#71717a |
Secondary text |
--bitcoin |
#F7931A |
Accent, CTAs, brand |
--success |
#22c55e |
Confirmed, pass |
--warning |
#eab308 |
Slow, caution |
--danger |
#ef4444 |
Stuck, error |
Fonts: Geist Sans (UI text) + Geist Mono (TXIDs, fees, hex, code).
After each phase, verify with these tests:
- Phase 1:
pnpm buildsucceeds with static export. Dark theme renders. Network selector works. - Phase 2: Fetch a known TXID from each network, log parsed response, verify types match.
- Phase 3: Diagnose known stuck/slow/fine/confirmed TXIDs, verify CheckResults and Verdict accuracy.
- Phase 4: Full visual flow: paste → animated steps → verdict card. Test all 4 verdict types. Mobile check.
- Phase 5: Generate PSBT for testnet4 tx, import into Sparrow, verify structure is valid.
- Phase 6: All 3 delivery methods work: QR scans, file imports, hex pastes correctly.
- Phase 7: Broadcast signed testnet4 tx, tracker shows confirmation.
- Phase 8: Receipt displays correct data, copy function works.
- Phase 9:
pnpm build && npx serve out— full end-to-end flow works from static build. Deploy to GitHub Pages succeeds.
Phase 1 (Foundation) ──→ Phase 2 (API) ──→ Phase 3 (Engine) ──→ Phase 4 (UI)
│
Phase 1 ──→ Phase 5 (PSBT) ──→ Phase 6 (Delivery) ──→ Phase 7 (Broadcast) ──→ Phase 8 (Receipt)
│
Phase 9 (Polish)
Phase 5 (PSBT) can start in parallel with Phase 4 (UI) since it only depends on Phase 2 (API types) and Phase 1 (polyfills).