Chapter 61 - Payments
61
Nostr Implementation Possibility - Hex № 61

Nutzaps

A nutzap is a P2PK-locked Cashu token published as a Nostr event. The payment itself is the receipt.

1 Overview

A nutzap sends ecash to another user over Nostr. The sender mints a Cashu token at a mint the recipient trusts. The sender locks the token to a public key the recipient controls. The sender then publishes the token inside a Nostr event.

The lock uses P2PK (pay-to-public-key, Cashu NUT-11). A P2PK-locked token can be spent only by the holder of the matching private key. So the published event is safe to show in public. It is money and proof of payment at the same time. No invoice, no callback server, and no receipt event from a third party are needed. Compare this with Lightning zaps in NIP-57, which need a server to issue receipts.

This NIP defines two events. kind:10019 tells the world how to send money to a user. kind:9321 carries the locked tokens to the recipient. Redemption reuses the kind:7376 spending-history event from NIP-60.

Hint!

Read NIP-60 first. It defines the wallet event that stores the private key nutzaps are locked to, and the token and history events a wallet uses. NIP-61 is the "receive money" side of that wallet.

Alice Mint Relay Bob mint P2PK token kind:9321 REQ swap
Fig. 61-1 — The nutzap loop. Alice mints a locked token, publishes it as an event, and Bob swaps it into his own wallet.

2 The Nutzap Info Event — Kind 10019

kind:10019 is a replaceable event. It tells other users how to send money to its author. A sender MUST read this event before sending a nutzap.

Wire format - kind 10019 (nutzap info)
{
  "kind": 10019,
  "tags": [
    ["relay", "wss://relay1"],
    ["relay", "wss://relay2"],
    ["mint", "https://mint1", "usd", "sat"],
    ["mint", "https://mint2", "sat"],
    ["pubkey", <p2pk-pubkey>]
  ]
}
Kind 10019 tags
TagValueRule
relayrelay URLA relay the user reads token events from. Senders should write nutzap events to these relays. Repeat the tag for each relay.
mintmint URL, then base unitsA mint the user explicitly agrees to receive funds on. Extra values list the base units the mint supports, such as usd or sat. Clients SHOULD NOT send money on a mint that is not listed here. Doing so risks burning the money.
pubkeypublic key, hexThe key that MUST be used to P2PK-lock incoming nutzaps. See the lock rules below.

P2PK lock rules

The lock target is strict. Follow these rules exactly:

  • Lock to the key in the pubkey tag of the recipient's kind:10019. This key corresponds to the privkey field encrypted inside the recipient's NIP-60 wallet event (kind:17375).
  • Implementations MUST NOT lock to the recipient's main Nostr public key.
  • When building the P2PK lock, clients MUST prefix the public key with "02". Cashu uses 33-byte compressed keys; Nostr keys are 32 bytes. The prefix keeps nostr and cashu compatible.
Warning!

Never lock a token to the recipient's Nostr identity key. The wallet's separate P2PK key exists so the user can spend ecash without exposing the identity key to wallet code, and can rotate it independently.

3 The Nutzap Event — Kind 9321

kind:9321 is the nutzap itself. The sender publishes it and p-tags the recipient. Its proofs are P2PK-locked to the key the recipient listed in kind:10019.

Wire format - kind 9321 (nutzap)
{
  "kind": 9321,
  "content": "Thanks for this great idea.",
  "pubkey": <sender-pubkey>,
  "tags": [
    ["proof", "{\"amount\":1,\"C\":\"02277c…6d3f\",\"id\":\"000a93d6f8a1d2c4\",
      \"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"b00b…ee83\\\",
      \\\"data\\\":\\\"02eaee…c1ed\\\"}]\"}"],
    ["unit", "sat"],
    ["u", "https://stablenut.umint.cash"],
    ["e", <nutzapped-event-id>, <relay-hint>],
    ["k", <nutzapped-kind>],
    ["p", <recipient-pubkey>]
  ]
}
Kind 9321 fields and tags
Field / TagRequiredRule
contentoptionalA comment for the nutzap. May be empty.
proofone or moreA Cashu proof, JSON-stringified. It MUST be P2PK-locked to the recipient's kind:10019 pubkey and MUST include a DLEQ proof (Cashu NUT-12).
unitoptionalThe base unit of the proofs, such as sat, usd, or eur. Defaults to sat when omitted.
urequiredThe URL of the mint, written EXACTLY as it appears in the recipient's kind:10019.
eoptionalThe event that is nutzapped, if any, with an optional relay hint.
koptionalThe kind of the nutzapped event.
prequiredThe Nostr identity public key of the nutzap recipient.
Tech Note

The source spec shows the k tag in its kind:9321 example but never defines it in the tag list. It was added in 2025 to match other reaction-style events; the meaning here is inferred. The prose for the u tag is also garbled in the source ("is the mint the URL of the mint").

4 Sending a Nutzap

Follow this procedure to send a nutzap:

  1. Fetch the recipient's kind:10019 event.
  2. Mint new ecash, or swap ecash you already hold, on one of the mints the recipient lists. P2PK-lock the outputs to the recipient's listed pubkey, with the "02" prefix.
  3. Build a kind:9321 event with the proofs, the u mint tag, and the p tag. Add e and k tags when you zap a specific event.
  4. Publish the event to the relays the recipient listed in kind:10019.
Warning!

A nutzap MUST use a mint from the recipient's kind:10019 and MUST reach the recipient's relays. Break either rule and the recipient may never see the event. The tokens are then donated to the mint forever.

5 Receiving and Redeeming

The recipient's client polls its relays for nutzaps. Clients should send a REQ that filters:

  • with #p for the user's own pubkey;
  • with #u for the mints the user expects ecash from. This stops the client from interacting with mints the user never signaled;
  • with since set to the created_at of the user's most recent kind:7376 event. That event marks the last redeemed nutzap. Clients may use other markers, including internal state; this is one possible approach.
Subscription filter - fetch pending nutzaps
{ "kinds": [9321], "#p": ["my-pubkey"], "#u": [<mint-1>, <mint-2>],
  "since": <latest-created_at-of-kind-7376> }

On each new nutzap, the client swaps the locked proofs into a wallet the user controls. That wallet can be a NIP-60 wallet, the user's own Lightning wallet, or anything else.

Recording the redemption — kind 7376

When it claims a token, the client SHOULD create a kind:7376 spending-history event (defined in NIP-60) and e-tag the original nutzap. This records that the token is already claimed, so no client tries again. It also signals to the sender that the ecash was redeemed. One kind:7376 event may tag multiple kind:9321 events.

Wire format - kind 7376 (redemption record)
{
  "kind": 7376,
  "content": nip44_encrypt([
    ["direction", "in"],                     // in = received, out = sent
    ["amount", "1"],
    ["unit", "sat"],
    ["e", <7375-event-id>, <relay-hint>, "created"]  // new token event
  ]),
  "tags": [
    ["e", <9321-event-id>, <relay-hint>, "redeemed"],  // nutzap redeemed
    ["p", <sender-pubkey>]                    // author of the 9321 event
  ]
}

The encrypted content lists the direction (in for received), the amount, the unit, and an e tag with marker created pointing to the new kind:7375 token event. The unencrypted tags carry the redeemed nutzap with marker redeemed and the sender's pubkey. Publish redemption events to the sender's NIP-65 "read" relays, so the sender learns the money arrived.

Sender Relays Recipient kind:10019 (mints, relays, pubkey) mint P2PK token at listed mint ["EVENT", kind:9321] ["REQ", {"#p","#u","since"}] ["EVENT", kind:9321] verify lock + DLEQ, swap into wallet ["EVENT", kind:7376 "redeemed"] 7376 goes to the sender's NIP-65 read relays
Fig. 61-2 — Full lifecycle. Info event, locked token, nutzap event, swap, and the redemption record.

6 Verifying a Nutzap

Any observer client can count the nutzaps an event received. Before it lists or counts a nutzap, the observer SHOULD run four checks:

  1. Check that the receiving user issued a kind:10019 that tags the mint where the ecash was minted.
  2. Check that the token is locked to the pubkey the user listed in that kind:10019.
  3. Check the u tag: the token must be issued by one of the mints listed in the kind:10019.
  4. Verify the DLEQ proof of the tokens locally.

All four checks work offline. The observer only needs the receiver's kind:10019 event and the mint's keyset. So verification is reasonably fast.

7 Final Considerations

  • Clients SHOULD guide users to list only mints compatible with NUT-11 (P2PK) and NUT-12 (DLEQ proofs) in their kind:10019. Otherwise anyone can spend the "locked" nutzaps.
  • Clients SHOULD normalize and deduplicate mint URLs, as described in NIP-65.
  • A nutzap event MUST include proofs from one of the mints the recipient listed in their kind:10019, and it MUST be published to the recipient's NIP-65 relays. Failure to do so may donate the tokens to the mint, because the recipient might never see the event.
Tech Note

The source spec names two different publish targets. The flow and the relay tag say: publish nutzaps to the relays in the recipient's kind:10019. The final considerations say: publish to the recipient's NIP-65 relays. Safe clients publish to both sets. Also, NIP-65 describes relay URL handling, not mint URL normalization; the reference is loose.

8 Worked Example

Alice liked Bob's note event-id-1. She nutzaps him 1 sat. The transcript below shows both sides. Orange lines go to a relay. Blue lines come from a relay.

Session transcript - Alice zaps, Bob redeems
-- Alice fetches Bob's nutzap info --
→ ["REQ", "info", {"kinds":[10019], "authors":["<bob>"]}]
← ["EVENT", "info", {"kind":10019, "tags":[["relay","wss://relay1"],
     ["mint","https://stablenut.umint.cash","sat"], ["pubkey","eaee89…ac1ed"]], …}]
-- Alice swaps 1 sat at that mint, locked to "02" + eaee89…ac1ed --

-- Alice publishes the nutzap to wss://relay1 --
→ ["EVENT", {"kind":9321, "content":"Thanks for this great idea.",
     "tags":[["proof","{\"amount\":1,\"C\":\"02277c…6d3f\",\"id\":\"000a93d6f8a1d2c4\",
     \"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"b00b…ee83\\\",\\\"data\\\":\\\"02eaee…c1ed\\\"}]\"}"],
     ["unit","sat"], ["u","https://stablenut.umint.cash"],
     ["e","event-id-1","wss://relay1"], ["k","1"], ["p","<bob>"]], …}]
← ["OK", "<nutzap-id>", true, ""]

-- Bob's client polls for unredeemed nutzaps --
→ ["REQ", "nz", {"kinds":[9321], "#p":["<bob>"],
     "#u":["https://stablenut.umint.cash"], "since":1749762600}]
← ["EVENT", "nz", {"kind":9321, …}]
← ["EOSE", "nz"]
-- Bob swaps the proof into his NIP-60 wallet (new kind:7375),
   then records the redemption --
→ ["EVENT", {"kind":7376, "content":"<nip44: direction=in, amount=1,
     unit=sat, e=<7375-id>/created>",
     "tags":[["e","<nutzap-id>","wss://relay1","redeemed"], ["p","<alice>"]], …}]

9 Version History

NIP-61 landed together with the NIP-60 Cashu wallet in late 2024 and was simplified heavily in early 2025. Notable changes, newest first:

  • 20252025-10Added the unit tag to the kind:9321 event, with sat as the default.
  • 20252025-08Added the missing k tag to the nutzap example, matching other reaction-style events.
  • 20252025-02Removed the a tag from nutzap events; a series of wording nitpicks tightened the lock and verification rules.
  • 20252025-02Major simplification pass shared with NIP-60: the redemption flow settled on the kind:7376 history event.
  • 20252025Typo, JSON-format, and heading-level fixes; an unfinished sentence completed.
  • Origin2024-10-25Pablo Fernandez proposed Cashu wallets and Nutzaps in one PR (#1369).

Original Proposal - Credits

AuthorPablo Fernandez
First commit2024-10-25 — “Cashu wallet + Nutzaps” (PR #1369)
Statusdraft - optional — a P2PK-Cashu payment layer on top of NIP-60
Source61.md on GitHub - full change history

10 See Also

  • NIP-60 — the Cashu wallet: the encrypted privkey, token events (kind:7375), and history events (kind:7376).
  • NIP-65 — relay list metadata; redemption events go to the sender's read relays.
  • NIP-57 — Lightning zaps; the server-based receipt flow nutzaps replace.
  • NIP-44 — the encryption used for the kind:7376 content.
  • NIP-01 — events, tags, filters, and the replaceable storage class of kind:10019.