Introduction
→ When I started Summer of Bitcoin, I knew Bitcoin at a high level, but I had never worked deeply with Cashu, Nostr event models, wallet identities, or escrow protocols.
→ My Phase 1 project was to implement end-to-end P2PK escrow support inside Shopstr, a decentralized marketplace built on Nostr and Cashu.
The Goal
→ The objective was simple from a user's perspective. Instead of immediately paying a seller after checkout, the system becomes:
Buyer
↓ Pay Invoice
Seller instantly receives funds
Buyer
↓ Creates locked Cashu proofs
Escrow
↓
Seller claims / Seller redeems / Buyer refunds
→ The challenge was implementing this safely while preserving compatibility with existing Shopstr users.
Phase 1 Timeline
→ The work evolved through multiple PRs instead of one massive change.
WalletConfig V1
The first challenge wasn't escrow. It was identity. Historically Shopstr identified wallets using the user's Nostr identity. That becomes problematic once proofs are locked cryptographically. I introduced WalletConfig V1, giving Shopstr a dedicated Cashu wallet identity (public key + private key), with migration support from legacy configuration.
Context Integration
Wallet identities then needed to become globally available. I extended the application's Cashu context so components could access cashuPubkey and cashuPrivkey without changing existing wallet behaviour. Backwards compatibility was essential throughout the migration.
Checkout
Checkout became significantly more complex. The buyer flow now generates reclaim keys, creates a locked proof, stores escrow metadata, and sends the locked proof, rather than just paying an invoice directly to the seller.
Claim & Redeem
Once the seller receives locked proofs, they aren't immediately spendable. I built two paths: Claim (unlock proofs into the Cashu wallet) and Redeem (unlock then immediately melt to Lightning). The important discovery was that both flows share the same cryptographic unlock process. Only the destination differs.
Refund Flow
Refunds looked easy. They weren't. The buyer can only recover funds if the locktime has expired, the seller never claimed, and the refund key matches. Designing eligibility safely required far more work than expected.
Problems I Faced
Wallet Identity Migration
How do existing users obtain a Cashu identity? The solution was: generate once, publish WalletConfig V1, never regenerate. That sounds simple. Later reviews revealed why it wasn't enough.
Escrow Records Were Write-Only
I stored escrow recovery metadata in a Nostr event. But after clearing browser storage, there was no local escrow and no reclaim button. The fix: restore records from local cache, restore from relays, and hydrate local storage automatically. Now reclaim survives browser resets and new devices.
Feature Flags
Checkout already respected the escrow feature flag. Other UI didn't. Result: a seller could enable escrow, a buyer could see escrow badges, and checkout would fail. The solution was a single source of truth so every escrow surface respected the same flag.
Wallet Identity Regeneration
This became the most interesting review discussion. Originally, a failed wallet load would trigger generation of a new identity. That's catastrophic for escrow: if an escrow references Key A and the wallet fails, Key B gets generated, and the refund disappears. The fix: generate a wallet only when fetch succeeded and no wallet exists. If loading fails, ask the user to retry.
Reviews Changed the Design
→ One thing I underestimated before Summer of Bitcoin was how valuable code review is. Most review comments were not "this code is wrong." They were "What happens if..."
→ Each review improved the architecture. The design that shipped is not the design I started with.
Testing
→ The project grew a comprehensive test suite covering wallet configuration, fetch services, checkout, claim, refund, and feature flags.
→ Beyond mocked tests, the implementation was validated against a live Cashu mint, confirming proof state transitions and locked proof handling worked end-to-end.
Lessons Learned
1Think in lifecycles
- Building escrow isn't adding a button
- It's designing the entire ownership lifecycle end-to-end
- Every stage (creation, locking, claiming, refunding) must be thought through before writing code
2Preserve ownership
- Changing identities is dangerous in escrow contexts
- Recovering identities safely is even harder
- The generation policy must protect existing escrow references at all costs
3Edge cases define production software
- The happy path worked early
- The difficult part was making failures safe
- Browser resets, relay outages, and new devices must all be handled correctly
4Reviews are design discussions
- Several important architectural improvements came directly from reviewer feedback
- The best review comments aren't corrections, they're 'what happens if...' questions
- That changed how I think about open source collaboration
What's Next
→ Phase 1 completed the full escrow lifecycle. Future work will continue improving reliability, testing, and the overall user experience.
→ The dispute resolution system and hodl escrow are still on the roadmap. The foundation is solid enough to build on.
Final Thoughts
→ This was the largest and most technically challenging open-source contribution I've worked on so far. It introduced me to Cashu, Nostr, wallet identity management, escrow design, distributed state recovery, and protocol-driven engineering.
→ More importantly, it changed how I think about software. Writing the feature was only half the work. Making it resilient, recoverable, and maintainable was the real engineering challenge.
Thanks to the Shopstr maintainers, reviewers, and the Summer of Bitcoin mentors for pushing the design further than I would have on my own.
