Inspiration
The global payments landscape is fragmented — billions of people use mobile money platforms like M-Pesa but have no reliable, trustless way to move value to and from Bitcoin. Existing peer-to-peer swap solutions rely on centralized coordinators, custodial escrow, or opaque off-chain messaging. We were inspired by the Pontmore protocol (PIP-02) and the Nostr ecosystem to explore whether you could build a fully transparent, sovereign swap coordination layer where every state transition is a verifiable, signed event on public relays — and private payment details stay truly private.
What it does
Pontswap is a Nostr-native coordination layer for Bitcoin ⇄ fiat swaps. It implements the Pontmore PIP-02 swap flow end to end in a standalone Next.js app. Two browser sessions — a customer and an agent — coordinate a fiat_to_btc swap where:
- Every public state change (swap request, accept, escrow funded, fiat sent, fiat confirmed, BTC released, completed) is a real signed Nostr event published to public relays (kinds 7300, 7301, 7302, 30362).
- Private payment instructions (e.g. M-Pesa details) travel exclusively through NIP-59 Gift Wrap — end-to-end encrypted, never touching public relays in readable form.
- An append-only, matrix-validated state machine enforces that only legal role-based transitions occur. Neither party can take an action outside their permitted role.
- A public swap explorer (
/swap/<id>/explorer) lets anyone verify the full state chain — and confirm no payment detail ever appears in a public event.
This v1 is a role-play demo (no real funds move), but every cryptographic primitive and relay interaction is real.
How we built it
We built Pontswap as a Next.js app with a clean separation between the Nostr protocol layer and the UI:
- State machine (
src/lib/pontmore/states.ts): defines the full swap state vocabulary and transition matrix withcanTransition,nextStatesFor, andreplayTransitionsutilities. - Event schemas (
src/lib/pontmore/kinds.ts): Zod schemas for every event payload and kind constant (7300/7301/7302/30362). - Relay layer (
relay.ts): a SimplePool wrapper handling connect, publish, query, and subscribe. - Signer (
signer.ts): a unified interface for NIP-07 browser extensions (Alby, nos2x) and in-memory dev keys, including NIP-44 support detection for Gift Wrap. - Gift Wrap (
gift-wrap.ts): NIP-59 assembled over the signer interface for private messaging. - Swap lifecycle (
swap.ts): request, transitions, evidence submission, snapshot publication, and real-time subscriptions.
We worked around a key relay limitation: NIP-01 relays only index single-letter tag filters, so multi-word tags like ["swap_id", id] can't be queried directly. We solved this by always co-publishing a ["d", swap_id] tag and filtering on {"#d": [swapId]}.
Challenges we ran into
- NIP-59 Gift Wrap complexity: Correctly assembling layered encrypted events (rumor → seal → gift wrap) with proper key management and NIP-44 payload encryption required careful implementation and testing.
- Relay tag filtering quirks: Discovering that
#swap_idreturns nothing on standard NIP-01 relays — and designing a robust workaround using thedtag — took significant debugging across multiple public relays. - Role-based UI gating: Ensuring each party only ever sees buttons for actions they are legally permitted to take (enforced by
canTransition) without leaking state information to the wrong party. - Identity management across two sessions: Coordinating two separate browser identities (NIP-07 extensions in separate profiles, or dev keys) while keeping the UX clear and warning users about NIP-44 support gaps.
Accomplishments that we're proud of
- A fully working, end-to-end Nostr-native swap coordination flow with real relay events and real cryptographic signatures.
- A clean, auditable state machine where the full swap history can be replayed and validated by anyone with the swap ID.
- True privacy for payment details: NIP-59 Gift Wrap means sensitive M-Pesa codes and payment instructions never appear in public events.
- A public swap explorer that provides transparency and auditability without exposing any private data.
What we learned
- The Nostr protocol's relay filtering model has meaningful constraints that require protocol-level design choices (like our
dtag workaround). - NIP-59 Gift Wrap is a powerful primitive, but its correct implementation requires careful attention to key derivation, payload structure, and extension compatibility.
- Building a role-based, append-only state machine on top of a permissionless pub/sub network forces you to think carefully about what "trustless" really means at the coordination layer.
What's next for Pontswap
- Real settlement: Integrate Lightning invoices and/or on-chain escrow to move real value.
- Dispute resolution: Implement PIP-03 for arbitration flows.
btc_to_fiatdirection: Complete the reverse swap path.- Agent publishing: Let agents publish their own 30360 availability and 30361 escrow descriptors directly from the app.
- Reputation and indexing: Build on the Pontmore indexing layer for agent discovery and trust signals.
Built With
- bitcoin
- next.js
- nostr
- react
- typescript
- websocket
- zod