Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The end-to-end payment flow

Summary. This page follows one payment from the moment you hold Send to the moment it settles on the Grin chain, through GRIM, Nostr, and Tor. It’s the single best way to see how the three pillars cooperate.

The cast

  • GRIM builds and finalizes the Grin slatepacks.
  • The Nostr protocol wraps each slatepack as an encrypted message.
  • The NostrService publishes and receives them.
  • Tor carries every byte, when Tor routing is on (the diagram below assumes it is; in clearnet mode the same legs go direct).
  • The ingest policy decides what each side does with what it receives.

Standard payment, step by step

Alice pays Bob 5 ツ by username.

ALICE                                                   BOB
  │ 1. resolve bob → npub (NIP-05, over Tor)
  │ 2. GRIM builds Standard-1 slatepack
  │ 3. gift-wrap (kind 1059) + publish ───┐
  │    to Bob's kind 10050 relays         │  over Tor
  │    over Tor                           └──────────────▶ 4. ingest: AutoReceive
  │                                                         5. GRIM builds Standard-2
  │ 7. ingest: FinalizePost  ◀──────────── gift-wrap ──────┘ 6. publish reply (over Tor)
  │ 8. GRIM finalizes the tx
  │ 9. broadcast to Grin node (direct)
  ▼ 10. both wallets see it confirm (10 blocks)
  1. Resolve. If you typed bob, the wallet resolves it to an npub via NIP-05, an HTTPS lookup that goes over Tor. (Paste an npub/nprofile and this is skipped; relay hints may come along for free.)
  2. Build leg 1. GRIM creates the Standard-1 slatepack for 5 ツ to Bob and records tx_meta (direction = Sent, status = Created).
  3. Wrap & send. The send pipeline builds a kind 14 rumor (preamble + slatepack + note), seals and gift-wraps it (NIP-59), and publishes the kind 1059 to your relays and Bob’s DM relays, all over Tor. Status → AwaitingS2. The send spinner completes as soon as a relay acknowledges the write, so the UI shows “Sent” promptly; a background read-back re-checks delivery without holding up the screen.
  4. Bob ingests. Bob’s wallet (even if just reconnected) pulls the gift wrap, unwraps it, parses the slate, and runs decide(). A new payment under the default policy → AutoReceive.
  5. Build leg 2. Bob’s GRIM builds the Standard-2 reply.
  6. Reply. Bob’s wallet gift-wraps and publishes it back to Alice’s relays, over Tor.
  7. Alice ingests the reply. Her wallet matches the Standard-2 to her pending tx and confirms it came from Bob’s npubFinalizePost.
  8. Finalize. Alice’s GRIM finalizes the transaction.
  9. Broadcast. The finalized tx is posted to the Grin node directly (public chain data; not over Tor).
  10. Confirm. Both wallets watch the chain; after the confirmation window the payment shows as settled.

Neither party pasted a slatepack, and neither needed the other online at the same instant: relays buffered the messages.

How the bytes travel

With Tor routing on, every message above rides Tor; the wraps themselves are NIP-44 encrypted (v3 when both wallets support it) either way. The primary relay, like every other relay, is then dialed over a Tor exit to its clearnet host, so the payment path never touches the clear net from the device, and it’s fast: the money-path relay connects in a few seconds even from a cold app start, and a funded payment finalizes in about eight seconds end to end.

Requests (invoice flow)

A request runs the same machinery with the roles inverted: you issue an Invoice-1 (“please pay me 5 ツ”), the payer’s wallet surfaces it for explicit approval (never auto-paid; see ingest policy), and on approval the Invoice-2/finalize legs complete. Declining or cancelling sends a void control message.

Payment proofs (on request)

Payments can include a native Grin payment proof when the payment request asks for one, off by default, shown on the review screen.

That is the whole rule. An ordinary person-to-person send carries no proof and is byte-identical to a send with the feature never involved. Proof mode is turned on for a single transaction only when the payment link that opened the review screen asks for it, by carrying the relevant parameters (a proof address, and optional order and notify hints). When it is on, the review screen shows a plain Payment proof: Included row so you can see it before you hold to send. The parsing is fail-closed: if any of those parameters is missing or malformed, the wallet drops proof mode and falls back to a normal proof-free payment rather than guessing. Requests you issue are unaffected; only a send whose link explicitly asks for a proof ever produces one.

Where it’s wired

  • UI dispatch: goblin/src/gui/views/goblin/send.rsWalletTask::NostrSend / NostrRequest / NostrPayRequest.
  • Task handling + finalize/broadcast: goblin/src/wallet/wallet.rs (the WalletTask::Nostr* arm, guarded wrappers nostr_receive / nostr_finalize_post / nostr_pay).
  • Wrap/unwrap + publish/subscribe: goblin/src/nostr/{protocol,client}.rs.
  • Accept/finalize decisions: goblin/src/nostr/ingest.rs.

References

  • The whole flow is exercised live by goblin/tests/nostr_e2e.rs (nip17_slatepack_roundtrip).
  • Slate stages (Standard-1/2, Invoice-1/2): Grin docs, https://docs.grin.mw.