Building Confidence in Jam: My Final Summer of Bitcoin Report
At the midterm, I wrote about returning to Jam after not being selected for Summer of Bitcoin the previous year. This final report is about what happened after that return became a real responsibility: building confidence in a Bitcoin privacy wallet through tests, safer workflows, clearer user feedback, and careful review.
My project was Comprehensive Test Coverage for the Core Send and Earn Flows, with CI Coverage Enforcement. The original scope was testing, but the test work gave me a broad understanding of Jam. After completing that scope, I used the remaining program time to improve production flows across the application.
What I Set Out To Do
Jam is a web interface for JoinMarket. Its Send and Earn flows sit close to wallet state, UTXOs, addresses, offers, and transactions. A regression in one of these areas can be more than a visual inconvenience, so the project needed tests that described user-visible behavior and failed reliably in CI.
I set out to:
- add meaningful automated coverage for the core Send and Earn workflows;
- cover surrounding wallet-critical components, hooks, stores, and utilities;
- test loading, error, empty, and interaction states instead of only happy paths;
- stabilize timing-sensitive tests and mocks; and
- enforce coverage thresholds in CI so future changes could not silently remove tested behavior.
Building The Test Foundation
The first milestone focused on shared code. PR #1267 added tests for utilities and reusable components, including behavior around copying values, formatting data, and common UI interactions. PR #1270 then made the CI coverage upload resilient by skipping it when an earlier failure prevented the coverage report from being generated. This removed a misleading secondary error and kept the original failure visible.
The main testing milestone was PR #1276. It touched 106 files while expanding tests across components, hooks, context providers, wallet flows, settings, onboarding, orderbook behavior, and shared state. At that merged checkpoint, combined unit and Storybook coverage reached 90.6% for statements, 90.7% for lines, 89.1% for functions, and 82.7% for branches. The PR also established 80% thresholds for statements, lines, functions, and branches.
The threshold was not the goal by itself. It was a guardrail. The useful part was identifying behavior that the application depended on and leaving behind a test that would explain when that behavior changed.
Major Technical And Design Decisions
Test Behavior, Not Implementation Details
Tests are easier to maintain when they describe what a user or caller can observe. I focused on questions such as:
- Does an error state explain what failed?
- Is an action disabled while wallet state makes it unsafe?
- Does a dialog reset after it closes?
- Does a loading state prevent a duplicate action?
- Does a route remain stable when authentication or developer mode changes?
This approach made the tests useful during later refactors because the implementation could change while the expected behavior stayed clear.
Standardize Validation At The Boundary
Send, Receive, Sweep, and Fidelity Bond forms had related validation rules but did not always express them in the same place. PR #1315 introduced shared validators for source jars and Bitcoin addresses. It also aligned address, network, reused-address, and amount checks across flows.
PR #1357 moved jar selection to React Hook Form and Yup, and PR #1358 applied the same form model to the remaining dialog flows. The important decision was not the library choice alone. It was ensuring that selection remained valid at submit time, even if wallet state changed after the dialog opened.
Reuse Transaction Logic Where The Risk Is Shared
PR #1345 reworked the Fidelity Bond creation, renewal, and move-to-jar experiences around shared dialog components and hooks. The renewal and move flows now share the sequence that freezes other UTXOs, unfreezes the bond, sends it, and restores the prior frozen state. Keeping that sequence in one place reduced duplicated transaction logic and made error handling consistent.
The same PR aligned step progress, jar selection, confirmation, success details, mobile layout, and footer actions across all three dialogs. This was a design and reliability improvement together: consistent interfaces are easier for users to understand and easier for maintainers to test.
Make Important State Visible
Several later changes followed one principle: do not make users infer wallet or network state when the application already knows it.
- PR #1367 blocks new receive-address requests while a wallet rescan is active.
- PR #1408 prevents users from entering Fidelity Bond creation without an eligible UTXO and explains how to obtain one.
- PR #1420 shows whether a maker’s offer appears in the local orderbook, including checking, visible, not-found, and error states.
- PR #1433 replaces arbitrary fee ranges with JoinMarket NG quantization bands, provides relative and absolute views, and adds accessible explanations for exact and rounded fees.
- PR #1441 creates the router once and moves state-dependent decisions into reactive guards, keeping route configuration stable as application state changes.
What Shipped
The agreed project scope is complete and merged. The final result includes:
- broad automated coverage for Send, Earn, and surrounding wallet-critical behavior;
- CI coverage enforcement across all four reported metrics;
- more stable asynchronous tests and clearer CI failures;
- shared form validation and standardized dialog form handling;
- unified Fidelity Bond workflows and responsive dialog behavior;
- safer receive-address behavior during rescans;
- clearer Fidelity Bond eligibility feedback;
- local maker-offer visibility and fee quantization views; and
- stable route configuration with regression coverage.
I also reviewed contributor PRs, investigated CI failures, discussed implementation choices, and helped work through review feedback. Those tasks were not a separate deliverable, but they were an important part of learning how a maintained open-source project moves forward.
What Remains
Nothing from the agreed primary testing scope remains unfinished or unmerged.
At the final report checkpoint on August 22, 2026, one additional security improvement, PR #1450, was under review. It removes a cached seed phrase when password verification expires, resets the reveal state after re-verification, and adds regression coverage for that timeout behavior. It is follow-up work beyond the original project scope.
Mentorship And Ownership
I owned the testing project from identifying coverage gaps through implementation, debugging, CI stabilization, review updates, and integration. As my understanding of the codebase grew, I also proposed and implemented production changes across wallet flows, validation, routing, orderbook views, and Fidelity Bond experiences.
My mentor, tbk, gave me substantial freedom while remaining available whenever I needed JoinMarket context, architectural direction, or a rigorous review. He did more than suggest changes: he explained the risks behind them, questioned assumptions, and pushed me to reason about edge cases and long-term maintainability. His guidance improved both my engineering judgment and my confidence as an open-source contributor.
What I Learned
The largest lesson was that tests are part of product design. A good test asks what the application promises to the user and records that promise in a form that future contributors can run.
I also learned that a coverage percentage needs context. High coverage can still miss the state transition that matters, while one focused regression test can protect a critical workflow. Thresholds are useful because they stop coverage from quietly moving backward, but thoughtful cases are what make the suite valuable.
Working on Jam also changed how I approach frontend engineering. Wallet state, privacy, and transaction behavior make seemingly small UI decisions important. A disabled button, a visible error, a stable route, or a restored frozen state can be part of protecting the user.
Finally, I learned what ownership in open source looks like: understand the problem, propose a direction, accept detailed review, revise the patch, and remain responsible for how the change fits the rest of the project.
What Happens Next
Summer of Bitcoin ends, but my work with Jam does not. I plan to continue contributing to the project, reviewing changes where I have context, and improving wallet reliability, security, and maintainability. I also want to carry the same habits into future Bitcoin work: verify assumptions, make important state visible, and leave behavior better protected than I found it.
For anyone interested in Bitcoin open source, the best starting point is not waiting until you feel fully ready. Read the code, run the tests, follow review conversations, and begin with one concrete problem. My own journey started with rejection and continued because I came back and kept contributing.
Thank you to Summer of Bitcoin, tbk, the Jam maintainers, and every contributor whose review or discussion helped me improve this work.