Reference implementation of Cashu micropayments for Blossom blob storage.
BUD-07 enables content monetization on Blossom servers using Cashu ecash tokens. Artists upload media with payment metadata, and listeners pay with Cashu tokens to access content.
Repository: github.com/musiquay/musiquay (lab/ directory)
| Component | Description |
|---|---|
| Bitcoin Core | Private regtest chain for testing |
| LND | Two Lightning nodes for payment channel |
| Gonuts | Cashu mint (forked for LND 0.21+ compatibility) |
| Orly | Nostr relay with BUD-07 Blossom payment gating |
Artist specifies minimum payment. Access blocked without valid token.
# Upload with payment requirement
curl -X PUT http://localhost:10547/upload \
-H "X-Artist-Pubkey: npub1..." \
-H "X-Min-Sats: 100" \
-H "X-Mint-Url: http://mint.example.com" \
--data-binary @song.mp3
# Download without token → 402 Payment Required
curl http://localhost:10547/blossom/<sha256>
# Download with valid Cashu token → 200 OK
curl -H "X-Cashu: cashuB..." http://localhost:10547/blossom/<sha256>
Content is freely accessible, but voluntary payments are accepted and forwarded to the artist.
# Upload as free content
curl -X PUT http://localhost:10547/upload \
-H "X-Artist-Pubkey: npub1..." \
-H "X-Min-Sats: 0" \
-H "X-Mint-Url: http://mint.example.com" \
--data-binary @song.mp3
# Download without token → 200 OK (free access)
curl http://localhost:10547/blossom/<sha256>
# Download with voluntary tip → 200 OK (token forwarded to artist)
curl -H "X-Cashu: cashuB..." http://localhost:10547/blossom/<sha256>
┌──────────┐ 1. Mint tokens ┌──────────┐
│ Listener │ ──────────────────────▶│ Mint │
│ (Wallet) │ ◀───────────────────── │ (Cashu) │
└────┬─────┘ Lightning invoice └──────────┘
│
│ 2. Request blob with X-Cashu token
▼
┌──────────┐ 3. Validate token ┌──────────┐
│ Blossom │ ───────────────────────▶│ Mint │
│ Server │ ◀────────────────────── │ │
└────┬─────┘ Swap/burn proofs └──────────┘
│
│ 4. Return content
▼
┌──────────┐
│ Listener │
└──────────┘
The minimum payment is 1 satoshi:
# Clone the lab
git clone git@github.com:musiquay/musiquay.git
cd musiquay/lab
git submodule update --init
# Enter devbox and build
devbox shell
just build-all
# Start everything
just start-all
# Run BUD-07 tests
just test-bud07
From empty environment to working stack:
Play reports (kind 3221) publish auditable play counts to the Nostr network - full spec in FEATURES_MUSIC.md:
a), artist pubkey (p), blob hash (x), blossom server, codecblind, host, statement tags) certifying the play without identifying the listener; free plays are self-attestedd:music status events - expiring, addressable, already defined. Play reports are the record; NIP-38 statuses are the live feed.The current orly BUD-07 implementation (pay-per-request token gating) predates the access model in FEATURES_ACCESS.md. The gap, in order of priority:
swapAtMint discards the swap response and blinding scalars, so the listener's proofs are burned and the new proofs are unclaimable; the artist is never paid. Must be fixed before any sale is real.X-Cashu token per request. Re-downloads must be free for entitled buyers.X-Cashu challenge is currently bespoke JSON; it should be a NUT-24 payment request, which requires NUT-18/NUT-26 creqA/creqB encoding. NUT-24 stays for rental stream buckets; ownership purchases move to the kind 3222 order in FEATURES_ACCESS.md. Note that the gonuts wallet does not implement NUT-18/26 today, so a client cannot yet complete the 402 handshake without adding it.seenTokens map is never pruned and not persisted; the mint is the real replay backstop, but the local map should be bounded.MinSats = 0 marker, so the documented optional-tip path is unreachable. The spec now defines tips explicitly: a tip is a payment leg recorded on the delivery receipt (amount_sats plus token hashes in paid), and it never creates or changes a filter entry — an asset is free if and only if no filter event exists for it. Implement the marker so the path is reachable.