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 with canTransition, nextStatesFor, and replayTransitions utilities.
  • 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_id returns nothing on standard NIP-01 relays — and designing a robust workaround using the d tag — 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 d tag 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_fiat direction: 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

Share this project:

Updates