Merge pull request #3720 from vitorpamplona/claude/bolt12-zaps-nip-naming-qzw8zi

Rename NIP-XX to NIP-B1 for BOLT12 Zaps specification
This commit is contained in:
Vitor Pamplona
2026-07-26 17:20:04 -04:00
committed by GitHub
82 changed files with 200 additions and 200 deletions
+8 -8
View File
@@ -1,6 +1,6 @@
# NWC BOLT12 payments (nostr-wallet-connect/nwc#2)
Status: **scoping** — no code yet. Depends on NIP-2421 (this branch) and the
Status: **scoping** — no code yet. Depends on NIP-B1 (this branch) and the
unmerged `nostr-wallet-connect/nwc#2` (adds `pay`/`receive` to NIP-47).
## Ecosystem status (2026-07-25) — the wallet gap for real testing
@@ -10,7 +10,7 @@ A real NWC-321 (`pay`/`receive`) service exists —
— which confirms the protocol we built (Phase 0) is real and adopted. **But it is
LND-backed:** its `pay` "selects and pays a BOLT11 `lightning` instruction from a
BIP-321 URI" and "BOLT12 `lno` instructions are not supported by this LND backend."
So it returns no BOLT12 invoice and no `payer_proof`, and can't drive the NIP-2421
So it returns no BOLT12 invoice and no `payer_proof`, and can't drive the NIP-B1
zap loop. The blocker is the **node backend**, not the protocol: a CLN- or
LDK-backed NWC-321 service (CLN has full BOLT12 and leads the payer-proof draft
bolts#1346) would support `lno` + return `payer_proof`. Until one exists, the
@@ -97,7 +97,7 @@ but is **not** wired into the pay path — we'd add the gate ourselves.
kind-10058 offer and the wallet supports `pay`, offer a BOLT12 zap.
1. Build + sign kind-9737 intent (amount, offer, p, e/a/k, zap_id, content).
2. `pay` with `payment=bitcoin:?lno=<offer>`, `amount`,
`payer_note="nostr:nipXX:<intent-id>"`.
`payer_note="nostr:nipB1:<intent-id>"`.
3. On success with a `payer_proof`, `Bolt12ZapEvent.build(intent, payerProof,
payerPubKey = own | null for anon)`, sign, publish to the recipient's inbox
relays.
@@ -114,8 +114,8 @@ but is **not** wired into the pay path — we'd add the gate ourselves.
1. **`payer_note` → `invreq_payer_note` is NOT guaranteed by nwc#2.** The spec only
says "if `payer_note` is not empty, the selected instruction MUST support
payer-provided messages" — it never states the note lands in the BOLT12
`invreq_payer_note`. NIP-2421 binds the zap to the intent through exactly that
field (`invreq_payer_note == nostr:nipXX:<intent-id>`). If a wallet routes
`invreq_payer_note`. NIP-B1 binds the zap to the intent through exactly that
field (`invreq_payer_note == nostr:nipB1:<intent-id>`). If a wallet routes
`payer_note` elsewhere, the returned `payer_proof` fails our validator and the
9736 is worthless. **Phase 2 feasibility hinges on this** — needs confirmation
in the nwc thread / a reference wallet, or a follow-up to nwc#2 to nail it down.
@@ -129,7 +129,7 @@ but is **not** wired into the pay path — we'd add the gate ourselves.
`quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md`). Real wallet
(selective-disclosure) proofs now reconstruct and verify, so a bound zap counts
locally as `cryptoVerified = true`.
4. **Maturity.** Both nwc#2 and NIP-2421 are unmerged; few/no wallets implement
4. **Maturity.** Both nwc#2 and NIP-B1 are unmerged; few/no wallets implement
`pay` today. Gate hard on capability (Phase 3) and keep the intent fallback.
## Resolution of risks #1/#2 (nwc#2 maintainer, 2026-07-24)
@@ -138,7 +138,7 @@ Asked on the nwc#2 thread. Maintainer confirmed:
- **`payer_note` → `invreq_payer_note`**: "that is the intention" for BOLT12 (the
field doubles as a general memo). So our zap binding
(`invreq_payer_note == nostr:nipXX:<intent-id>`) is the intended target.
(`invreq_payer_note == nostr:nipB1:<intent-id>`) is the intended target.
- **`payer_proof`**: "should be returned for successful bolt12 payments"; the
"optional if unavailable" wording only covers non-BOLT12 instruction types.
@@ -160,7 +160,7 @@ Chosen: full integration into the zap pipeline, preferring BOLT12 when the
recipient offers it.
- `Account.sendBolt12Zap(...)` — signs a 9737 intent (ephemeral key for anonymous),
pays over NWC with `payer_note = nostr:nipXX:<intent-id>`, and only on a proof
pays over NWC with `payer_note = nostr:nipB1:<intent-id>`, and only on a proof
that passes `Bolt12ZapValidator` self-consumes + publishes the 9736 via
`computeRelayListToBroadcast`. No proof / invalid proof → "paid, no receipt".
- `ZapPaymentHandler.zap()` resolves each recipient's kind:10058 offer and
@@ -70,7 +70,7 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -369,9 +369,9 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.builder.Bolt12ZapBuilder
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.builder.Bolt12ZapBuilder
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.utils.DualCase
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.RandomInstance
@@ -1455,7 +1455,7 @@ class Account(
}
/**
* Sends a NIP-XX BOLT12 zap to [recipientPubKey] over the default NWC wallet.
* Sends a NIP-B1 BOLT12 zap to [recipientPubKey] over the default NWC wallet.
*
* Signs a kind 9737 intent, pays [offer] via the nwc#2 `pay` method with the
* intent-bound `payer_note`, then only if the wallet returns a payer proof that
@@ -75,7 +75,7 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
@@ -392,6 +392,10 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRejectEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRenegotiateEvent
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.OnchainBackend
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
@@ -401,10 +405,6 @@ import com.vitorpamplona.quartz.nipF4Podcasts.authored.AuthoredPodcastsEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.favorites.FavoritePodcastsListEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
import com.vitorpamplona.quartz.utils.DualCase
@@ -473,7 +473,7 @@ object LocalCache : ILocalCache, ICacheProvider {
val onchainZapResolver = OnchainZapResolver(this)
/**
* NIP-XX BOLT12 zap validator. Unlike onchain zaps, BOLT12 proof verification
* NIP-B1 BOLT12 zap validator. Unlike onchain zaps, BOLT12 proof verification
* is synchronous (a self-contained `lnp` payer proof), so `consume(Bolt12ZapEvent)`
* validates inline and needs no async resolver.
*/
@@ -1294,7 +1294,7 @@ object LocalCache : ILocalCache, ICacheProvider {
}
is Bolt12ZapEvent -> {
// NIP-XX BOLT12 zaps target an event (e), an addressable event (a),
// NIP-B1 BOLT12 zaps target an event (e), an addressable event (a),
// or just the recipient profile (p) — same shape as onchain zaps.
buildList {
event.zappedEvent()?.let { checkGetOrCreateNote(it)?.let { add(it) } }
@@ -2718,7 +2718,7 @@ object LocalCache : ILocalCache, ICacheProvider {
if (!(wasVerified || justVerify(event))) return false
// NIP-XX validation is fully synchronous: zap-event structure, the embedded
// NIP-B1 validation is fully synchronous: zap-event structure, the embedded
// kind:9737 intent match, and the `lnp` payer-proof binding + crypto. A failed
// validation drops the zap entirely — it never contributes to a zap total.
// The outer event signature was already verified above (wasVerified/justVerify),
@@ -24,7 +24,7 @@ import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
* The logged-in user's NIP-XX BOLT12 offer list (kind 10058) as live account state,
* The logged-in user's NIP-B1 BOLT12 offer list (kind 10058) as live account state,
* mirroring [com.vitorpamplona.amethyst.model.nipA3PaymentTargets.NipA3PaymentTargetsState].
* Exposes the current offers as a [flow], persists them across restarts (via
* [AccountSettings]), and publishes updates with [saveOffers].
@@ -77,7 +77,7 @@ class ZapPaymentHandler(
val user: User? = null,
)
/** A recipient routed over BOLT12 (NIP-XX): they publish a kind:10058 [offer] and we hold an NWC wallet. */
/** A recipient routed over BOLT12 (NIP-B1): they publish a kind:10058 [offer] and we hold an NWC wallet. */
data class Bolt12Recipient(
val user: User,
val offer: String,
@@ -442,7 +442,7 @@ class ZapPaymentHandler(
}
/**
* BOLT12 zap rail (NIP-XX). For each recipient that publishes a kind:10058 offer,
* BOLT12 zap rail (NIP-B1). For each recipient that publishes a kind:10058 offer,
* signs a 9737 intent, pays the offer over NWC with the intent-bound `payer_note`,
* and (if the returned proof validates) publishes a 9736 zap see
* [Account.sendBolt12Zap]. Fire-and-forget like [payViaNWC]: dispatch is optimistic
@@ -61,9 +61,9 @@ import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -52,8 +52,8 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
val AccountInfoAndListsFromKeyKinds =
listOf(
@@ -56,9 +56,9 @@ import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
import com.vitorpamplona.quartz.nip88Polls.response.PollResponseEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
/**
* Kinds that notify me about activity on MY messages inside a NIP-29 group reactions, replies
@@ -37,7 +37,7 @@ import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip58Badges.award.BadgeAwardEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.mapOfSet
val RepliesAndReactionsToAddressesKinds1 =
@@ -43,8 +43,8 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip88Polls.response.PollResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.contentDiscoveryResponse.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.status.NIP90StatusEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.mapOfSet
val RepliesAndReactionsKinds =
@@ -35,7 +35,7 @@ import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
import com.vitorpamplona.quartz.nip39ExtIdentities.ExternalIdentitiesEvent
import com.vitorpamplona.quartz.nip61Nutzaps.info.NutzapInfoEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.utils.mapOfSet
val UserMetadataForKeyKinds =
@@ -25,14 +25,14 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* Edits the logged-in user's NIP-XX BOLT12 offer list (kind 10058). Mirrors
* Edits the logged-in user's NIP-B1 BOLT12 offer list (kind 10058). Mirrors
* [com.vitorpamplona.amethyst.ui.actions.paymentTargets.PaymentTargetsViewModel];
* each entry is a canonical raw `lno1...` offer string.
*/
@@ -44,7 +44,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.Size35Modifier
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -351,12 +351,12 @@ import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.metadata.Podcasting20PodcastMetadata
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
@@ -550,7 +550,7 @@ fun payViaIntent(
* parameter of a BIP21/BIP321 bitcoin URI (`bitcoin:?lno=lno1`), where the on-chain
* address is optional so a Lightning-only offer stands on its own. The wallet resolves
* the offer, collects the amount, and completes the payment; this is a plain intent,
* not a NIP-57/NIP-XX zap, so it produces no Nostr receipt.
* not a NIP-57/NIP-B1 zap, so it produces no Nostr receipt.
*/
fun payViaBolt12Intent(
offer: String,
@@ -40,11 +40,11 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.bitcoinColor
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import java.text.NumberFormat
/**
* Standalone card for a NIP-XX BOLT12 zap (kind 9736), styled like the NIP-57
* Standalone card for a NIP-B1 BOLT12 zap (kind 9736), styled like the NIP-57
* lightning-zap card but labeled BOLT12. The sender is the `P` payer tag (or the
* event pubkey when anonymous); the amount comes straight off the validated
* `amount` tag no LNURL provider or private-zap decryption is involved.
@@ -1207,7 +1207,7 @@ class AccountViewModel(
/**
* Pays a recipient's BOLT12 [offer] over the default NWC wallet using the nwc#2
* `pay` method, wrapping it as a BIP321 `bitcoin:?lno=` instruction. This is a
* plain payment, not a NIP-XX zap (no Nostr receipt); the outcome is surfaced as
* plain payment, not a NIP-B1 zap (no Nostr receipt); the outcome is surfaced as
* a toast. Callers should gate on [hasNwcWallet].
*/
fun payBolt12OfferViaNwc(
@@ -26,8 +26,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
/**
* Fetches the NIP-75 zap goal referenced by a live stream plus the zap receipts
@@ -28,8 +28,8 @@ import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessa
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
import com.vitorpamplona.quartz.nip53LiveActivities.raid.LiveActivitiesRaidEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
fun filterMessagesToLiveActivities(
channel: LiveActivitiesChannel,
@@ -39,7 +39,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessa
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
/**
* Per-room state for every wire subscription scoped to a single
@@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessa
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresenceEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@@ -40,8 +40,8 @@ import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -86,11 +86,11 @@ import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -26,8 +26,8 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
val UserProfileZapReceiverKinds = listOf(LnZapEvent.KIND, OnchainZapEvent.KIND, Bolt12ZapEvent.KIND)
@@ -68,7 +68,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import kotlinx.coroutines.launch
@Composable
@@ -31,8 +31,8 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
@@ -163,13 +163,13 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
import com.vitorpamplona.quartz.nipF4Podcasts.authored.AuthoredPodcastsEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.favorites.FavoritePodcastsListEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
/** Returns the `@StringRes` id for the translated kind name, or -1 if unknown. */
@Suppress("DEPRECATION")
@@ -344,12 +344,12 @@ import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.metadata.Podcasting20PodcastMetadata
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
@@ -62,7 +62,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
+1 -1
View File
@@ -672,7 +672,7 @@ output then also reports `paid` + the preimage.
| `amy zap user USER SATS [--comment X] [--anon\|--private] [--with NDEBIT] [--timeout SECS]` | Profile zap: build the zap request and fetch a BOLT11 from USER's LN service. |
| `amy zap event EVENT-ID SATS [--comment X] [--anon\|--private] [--with NDEBIT] [--timeout SECS]` | Same, attributed to a specific event (must be in the local store). Zap splits are honored — one invoice per recipient. |
### BOLT12 zaps (NIP-XX)
### BOLT12 zaps (NIP-B1)
BOLT12 zaps (kinds 9736/9737, offers in kind:10058). amy has no NWC payment
rail, so sending is a **two-step, out-of-band** flow: `bolt12 intent` signs the
+1 -1
View File
@@ -62,7 +62,7 @@ Status legend: ✅ shipped · 📦 logic lives in `commons/`, needs a command ·
| Buzz workspaces (`amy buzz`) | ✅ | `BuzzCommands` — post/read the kind:40002 stream timeline, `attest` (offline NIP-OA), `console` (decrypt+aggregate kind:44200 turn metrics via the shared `AgentFleetAggregator`), `personas` (kind:30175). Join/leave reuse `amy relaygroup` (Buzz workspaces are NIP-29 groups). |
| NIP-51 lists (bookmarks, mute, follow sets) | 🆕 | `amethyst/model/nip51Lists/` |
| NIP-57 zaps (send) | ✅ partial | `ZapCommand``zap user`/`zap event` build the kind:9734 request and fetch the BOLT11 (zap splits honored, one invoice per recipient); `--with NDEBIT` auto-pays through a CLINK debit pointer. Receipt (kind:9735) verification still 🆕. |
| BOLT12 zaps (NIP-XX, kinds 9736/9737/10058) | ✅ partial | `Bolt12Commands` + `Bolt12SendCommands` over shared `commons` `Bolt12ZapActions``bolt12 decode` (offer/proof), `verify` (validate a kind:9736), `offer get/set` (kind:10058), and the two-step send `intent``zap` (out-of-band proof, since amy has no NWC rail). Interop harness + NWC-fetched proofs still 🆕. |
| BOLT12 zaps (NIP-B1, kinds 9736/9737/10058) | ✅ partial | `Bolt12Commands` + `Bolt12SendCommands` over shared `commons` `Bolt12ZapActions``bolt12 decode` (offer/proof), `verify` (validate a kind:9736), `offer get/set` (kind:10058), and the two-step send `intent``zap` (out-of-band proof, since amy has no NWC rail). Interop harness + NWC-fetched proofs still 🆕. |
| NIP-65 outbox model queries | ✅ | `OutboxCommand``amy outbox USER [--refresh]`, cache-first. |
| CLINK offers + debits (`amy offer` / `amy debit`) | ✅ | `OfferCommands` + `DebitCommands` — pointer decode, NIP-05 discover, kind:21001/21002 round-trips, `offer pay --with NDEBIT` end-to-end settlement. `--timeout` is SECONDS. |
| Geochat (Bitchat geohash, ephemeral kind:20000) | ✅ | `GeochatCommands` — listen/send/keys with per-geohash throwaway identity + geo-nearest relay routing; doubles as the Bitchat interop harness. |
@@ -750,7 +750,7 @@ private fun printUsage() {
| [--comment X] [--anon|--private] event (must be in local store)
| [--timeout SECS]
|
|BOLT12 zaps (NIP-XX):
|BOLT12 zaps (NIP-B1):
| bolt12 decode LNO1|LNP1 decode a BOLT12 offer or payer proof
| bolt12 verify EVENT-ID validate a kind:9736 zap in the local store
| bolt12 offer get USER fetch + show a user's kind:10058 offers
@@ -28,13 +28,13 @@ import com.vitorpamplona.amethyst.commons.actions.Bolt12ZapActions
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
/**
* `amy bolt12 ` NIP-XX BOLT12 zaps from the command line, for headless interop
* `amy bolt12 ` NIP-B1 BOLT12 zaps from the command line, for headless interop
* testing of the quartz BOLT12 stack.
*
* Sub-verbs:
@@ -54,7 +54,7 @@ import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
object Bolt12Commands {
val USAGE: String =
"""
|BOLT12 zaps (NIP-XX):
|BOLT12 zaps (NIP-B1):
| bolt12 decode LNO1|LNP1 decode an offer or payer proof to fields
| bolt12 verify EVENT-ID validate a kind:9736 zap in the local store
| bolt12 offer get USER fetch + show a user's kind:10058 offers
@@ -28,9 +28,9 @@ import com.vitorpamplona.amethyst.commons.actions.Bolt12ZapActions
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
/**
* The BOLT12 send-side sub-verbs of `amy bolt12` (split from [Bolt12Commands] to keep
@@ -24,19 +24,19 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipXXBolt12Zaps.builder.Bolt12ZapBuilder
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.builder.Bolt12ZapBuilder
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Hex
/**
* NIP-XX BOLT12 zap building, decoding, and validation the shared, UI-free
* NIP-B1 BOLT12 zap building, decoding, and validation the shared, UI-free
* surface a non-Android caller (amy CLI, interop harnesses) drives.
*
* Assembly-only: it re-exposes [Bolt12ZapBuilder] / [Bolt12ZapValidator] and the
@@ -23,12 +23,12 @@ package com.vitorpamplona.amethyst.commons.model
import androidx.compose.runtime.Stable
/**
* Per-payment NIP-XX BOLT12 zap entry attached to a target Note.
* Per-payment NIP-B1 BOLT12 zap entry attached to a target Note.
*
* Unlike NIP-BC onchain zaps (which carry an async chain-verification state
* machine), a BOLT12 zap is validated **synchronously** at consumption time
* the `lnp` payer proof is a self-contained cryptographic settlement proof so
* every entry stored here has already passed [com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator]
* every entry stored here has already passed [com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator]
* and its amount is counted directly, the same way a NIP-57 lightning zap
* receipt's amount is.
*
@@ -287,7 +287,7 @@ open class Note(
private set
/**
* NIP-XX BOLT12 zaps (kind 9736) targeting this note.
* NIP-B1 BOLT12 zaps (kind 9736) targeting this note.
* Key: the payer proof's `invoice_payment_hash` (hex) the spec's dedup key,
* so two zap events proving the same settled payment collapse to one entry.
* Value: entry with the source Bolt12ZapEvent note (so `source.author` is the
@@ -829,7 +829,7 @@ open class Note(
} else {
// Same verification status: dedup by the settled payment and, if amounts
// differ, keep the LOWER — so a re-publish with a bigger amount tag can't
// inflate the total (NIP-XX). Both share the same cryptoVerified flag.
// inflate the total (NIP-B1). Both share the same cryptoVerified flag.
if (entry.amountMillisats < existing.amountMillisats) entry else existing
}
if (merged == existing) return@withLock false
@@ -846,10 +846,10 @@ open class Note(
}
/**
* Register a NIP-XX BOLT12 zap targeting this note. [source] is the kind:9736
* Register a NIP-B1 BOLT12 zap targeting this note. [source] is the kind:9736
* event's own note `source.author` is the payer shown in the reactions
* gallery and notifications. [amountMillisats] and [cryptoVerified] come from
* the synchronous [com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator]
* the synchronous [com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator]
* verdict; the caller MUST only call this for a `Valid` result. Deduplicated by
* [paymentHashHex] (the proof's `invoice_payment_hash`).
*/
@@ -1189,7 +1189,7 @@ open class Note(
sumOfAmounts += BigDecimal(entry.claimedSats)
}
// NIP-XX BOLT12 zaps — count only the crypto-verified ones. An unverified
// NIP-B1 BOLT12 zaps — count only the crypto-verified ones. An unverified
// (compressed, or offer-unbindable) proof carries a self-chosen preimage,
// amount, and payment hash with no settled-payment guarantee, so counting it
// would let anyone inflate a note's total for free. Unverified entries stay
@@ -30,8 +30,8 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet
@@ -41,7 +41,7 @@ import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip61Nutzaps.info.NutzapInfoEvent
import com.vitorpamplona.quartz.nip61Nutzaps.info.tags.NutzapMintTag
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.utils.Hex
import kotlin.concurrent.Volatile
@@ -116,7 +116,7 @@ class User(
fun nutzapInfo() = nutzapInfoNote.event as? NutzapInfoEvent
/** This user's published BOLT12 offer list (NIP-XX kind 10058), or null if none seen. */
/** This user's published BOLT12 offer list (NIP-B1 kind 10058), or null if none seen. */
fun bolt12OfferList() = bolt12OfferListNote.event as? Bolt12OfferListEvent
/** The canonical raw BOLT12 offers (`lno1...`) this user accepts, empty when none published. */
@@ -35,8 +35,8 @@ import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
/**
* Nostr event kinds that can generate a notification when they tag the
@@ -71,7 +71,7 @@ object NotificationKinds {
NutzapEvent.KIND, // 9321 — NIP-61 Cashu nutzap
LnZapEvent.KIND, // 9735 — NIP-57 zap receipt
OnchainZapEvent.KIND, // 8333 — onchain zap
Bolt12ZapEvent.KIND, // 9736 — NIP-XX BOLT12 zap
Bolt12ZapEvent.KIND, // 9736 — NIP-B1 BOLT12 zap
// NIP-17 file-header messages (encrypted file DMs)
ChatMessageEncryptedFileHeaderEvent.KIND,
)
@@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ZapContribution
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.Job
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.commons.viewmodels
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
/**
* One in-flight kind-9735 zap to render as a floating overlay on the
@@ -22,11 +22,11 @@ package com.vitorpamplona.amethyst.commons.actions
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Values
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvStream
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Values
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.TlvStream
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -26,7 +26,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Verifies how validated NIP-XX BOLT12 zaps fold into a Note's aggregate zap
* Verifies how validated NIP-B1 BOLT12 zaps fold into a Note's aggregate zap
* accounting the model contract the LocalCache ingest path and the reaction-row
* counter depend on.
*/
@@ -57,7 +57,7 @@ class NoteBolt12ZapTest {
@Test
fun keepsTheLowerAmountWhenTwoProofsShareAPaymentHash() {
// NIP-XX: same proof identifier, differing amounts → count the LOWER, in either order.
// NIP-B1: same proof identifier, differing amounts → count the LOWER, in either order.
val a = note("a".repeat(64))
a.addBolt12Zap(note("b".repeat(64)), "h", amountMillisats = 21_000_000L, cryptoVerified = true)
a.addBolt12Zap(note("c".repeat(64)), "h", amountMillisats = 5_000_000L, cryptoVerified = true)
@@ -45,7 +45,7 @@ import com.vitorpamplona.nestsclient.connectReconnectingNestsSpeaker
import com.vitorpamplona.nestsclient.transport.WebTransportFactory
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
@@ -294,6 +294,8 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRejectEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRenegotiateEvent
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
@@ -303,8 +305,6 @@ import com.vitorpamplona.quartz.nipF4Podcasts.authored.AuthoredPodcastsEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.favorites.FavoritePodcastsListEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
@@ -541,8 +541,8 @@ object KindNames {
RelayAddMemberEvent.KIND to KindName("Relay Add Member", "43"),
RelayRemoveMemberEvent.KIND to KindName("Relay Remove Member", "43"),
OnchainZapEvent.KIND to KindName("Onchain Zap", "BC"),
Bolt12ZapEvent.KIND to KindName("Bolt12 Zap", "XX"),
Bolt12OfferListEvent.KIND to KindName("Bolt12 Offers", "XX"),
Bolt12ZapEvent.KIND to KindName("Bolt12 Zap", "B1"),
Bolt12OfferListEvent.KIND to KindName("Bolt12 Offers", "B1"),
PutUserEvent.KIND to KindName("Group Put User", "29"),
RemoveUserEvent.KIND to KindName("Group Remove User", "29"),
EditMetadataEvent.KIND to KindName("Group Edit Metadata", "29"),
@@ -51,7 +51,7 @@ class PayInvoiceMethod(
// (bolt11 `lightning=`, BOLT12 `lno=`, or on-chain). `payment` is the BIP321 URI;
// `amount` (msats) is required only when the instruction has no amount; `payer_note`
// is delivered to the payee when the instruction supports payer messages (for a
// BOLT12 zap this carries `nostr:nipXX:<intent-id>`).
// BOLT12 zap this carries `nostr:nipB1:<intent-id>`).
class PayParams(
var payment: String? = null,
var amount: Long? = null,
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
import com.vitorpamplona.quartz.nip19Bech32.bech32.Bech32
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
import com.vitorpamplona.quartz.utils.sha256.sha256
@@ -18,10 +18,10 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
/**
* A parsed BOLT12 offer (`lno1...`). Exposes the fields NIP-XX cares about; the
* A parsed BOLT12 offer (`lno1...`). Exposes the fields NIP-B1 cares about; the
* full TLV stream is retained for callers that need more.
*
* See https://github.com/lightning/bolts/blob/master/12-offer-encoding.md#offer-fields
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
/**
* A parsed BOLT12 payer proof (`lnp1...`), per lightning/bolts#1346.
@@ -108,7 +108,7 @@ class Bolt12PayerProof(
/**
* True when every field the BOLT12 crypto verification requires is present and
* well-sized (lightning/bolts#1346 reader rules). `invreq_payer_note` is *not*
* required here the NIP-XX zap binding checks it separately in the validator.
* required here the NIP-B1 zap binding checks it separately in the validator.
*/
fun hasAllCryptoFields(): Boolean =
invreqPayerId() != null &&
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
/**
* Mints a canonical BOLT12 `lnp1...` payer proof from a full invoice, per
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
/**
* BOLT-1 BigSize codec: a variable-length unsigned integer, big-endian, with a
@@ -18,21 +18,21 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.builder
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.builder
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.RandomInstance
import com.vitorpamplona.quartz.utils.TimeUtils
/**
* Assembles the two NIP-XX events in the order the payment flow requires:
* Assembles the two NIP-B1 events in the order the payment flow requires:
*
* 1. [buildIntent] sign a kind 9737 zap intent *before* paying.
* 2. Pay the offer, putting [payerNote] in the BOLT12 `invreq_payer_note`, and
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.intent
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
@@ -36,18 +36,18 @@ import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.toETag
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.ZapIdTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.ZapIdTag
import com.vitorpamplona.quartz.utils.TimeUtils
/**
* NIP-XX: BOLT12 Zaps the **zap intent** (kind 9737).
* NIP-B1: BOLT12 Zaps the **zap intent** (kind 9737).
*
* The payer creates and signs this event *before* paying the BOLT12 offer. It
* binds the payer's Nostr key to the recipient, the target, the amount and the
* offer. The payer then references this event's id in the BOLT12
* `invreq_payer_note` (`nostr:nipXX:<zap-intent-event-id>`), and finally embeds
* `invreq_payer_note` (`nostr:nipB1:<zap-intent-event-id>`), and finally embeds
* the whole serialized intent inside the kind 9736 zap event's `description` tag
* the same embedding pattern NIP-57 uses for the zap request.
*
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.intent
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
@@ -26,9 +26,9 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.ZapIdTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.ZapIdTag
fun TagArrayBuilder<Bolt12ZapIntentEvent>.recipient(recipientPubKey: HexKey) = addUnique(PTag.assemble(recipientPubKey, null))
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.offer
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Address
@@ -27,11 +27,11 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.utils.TimeUtils
/**
* NIP-XX: BOLT12 Zaps a user's **BOLT12 offer list** (kind 10058).
* NIP-B1: BOLT12 Zaps a user's **BOLT12 offer list** (kind 10058).
*
* A replaceable event, anchored to the author's pubkey, that publishes one or more
* canonical raw BOLT12 offers (`["offer", "lno1..."]`). This is how a payer
@@ -18,13 +18,13 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
/**
* The `amount` tag of a NIP-XX BOLT12 zap or zap intent: the payment amount in
* The `amount` tag of a NIP-B1 BOLT12 zap or zap intent: the payment amount in
* **millisatoshis** (not sats this matches the BOLT12 `invoice_amount` field it
* is validated against, and NIP-57's `amount` tag).
*/
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
@@ -18,10 +18,10 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.utils.ensure
/**
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
@@ -18,10 +18,10 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.utils.ensure
/**
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.tags
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.Hex
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import androidx.compose.runtime.Immutable
@@ -18,12 +18,12 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01Crypto
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Merkle
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Merkle
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.utils.sha256.sha256
/**
@@ -18,12 +18,12 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
/** Result of validating a NIP-XX BOLT12 zap event (kind 9736). */
/** Result of validating a NIP-B1 BOLT12 zap event (kind 9736). */
@Immutable
sealed interface Bolt12ZapValidation {
/**
@@ -18,20 +18,20 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import com.vitorpamplona.quartz.nip01Core.crypto.verify
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Hex
/**
* NIP-XX validator: runs the spec's validation steps over a [Bolt12ZapEvent] and
* NIP-B1 validator: runs the spec's validation steps over a [Bolt12ZapEvent] and
* returns a [Bolt12ZapValidation]. A client MUST validate an event with this (and
* deduplicate the results by [Bolt12ZapValidation.Valid.paymentHashHex]) before
* counting a BOLT12 zap.
@@ -47,7 +47,7 @@ import com.vitorpamplona.quartz.utils.Hex
* 5. the raw BOLT12 offer parses;
* 6. the `lnp` payer proof decodes and its crypto checks pass (see
* [Bolt12ProofVerifier]);
* 7. the proof binds to this zap: `invreq_payer_note == nostr:nipXX:<intent-id>`,
* 7. the proof binds to this zap: `invreq_payer_note == nostr:nipB1:<intent-id>`,
* `invoice_amount == amount`, and the proof matches the offer.
*/
class Bolt12ZapValidator(
@@ -236,9 +236,9 @@ class Bolt12ZapValidator(
/**
* The NIP binds the Lightning payment to the signed intent through the
* BOLT12 `invreq_payer_note`, which MUST equal this prefix followed by the
* intent event id. The `nipXX` segment tracks the final NIP number.
* intent event id. The `nipB1` segment is the assigned NIP-B1 number.
*/
const val NIP_URI_PREFIX = "nostr:nipXX:"
const val NIP_URI_PREFIX = "nostr:nipB1:"
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.zap
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -34,17 +34,17 @@ import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.PayerTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.ProofTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.PayerTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.ProofTag
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
/**
* NIP-XX: BOLT12 Zaps the **zap event** (kind 9736).
* NIP-B1: BOLT12 Zaps the **zap event** (kind 9736).
*
* A public, self-verifying proof that a BOLT12 payment was made to the author of
* a profile, event, or addressable event. It carries:
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.zap
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
@@ -26,11 +26,11 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.PayerTag
import com.vitorpamplona.quartz.nipXXBolt12Zaps.tags.ProofTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.DescriptionTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.PayerTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.ProofTag
fun TagArrayBuilder<Bolt12ZapEvent>.description(serializedIntentEventJson: String) = addUnique(DescriptionTag.assemble(serializedIntentEventJson))
@@ -386,6 +386,9 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRejectEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRenegotiateEvent
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
@@ -395,9 +398,6 @@ import com.vitorpamplona.quartz.nipF4Podcasts.authored.AuthoredPodcastsEvent
import com.vitorpamplona.quartz.nipF4Podcasts.episode.PodcastEpisodeEvent
import com.vitorpamplona.quartz.nipF4Podcasts.favorites.FavoritePodcastsListEvent
import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.offer.Bolt12OfferListEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
@@ -95,31 +95,31 @@ class RequestTest {
@Test
fun testPayCreateWithOfferAndPayerNote() {
val request = PayMethod.create("bitcoin:?lno=lno1abc", amount = 21_000L, payerNote = "nostr:nipXX:deadbeef")
val request = PayMethod.create("bitcoin:?lno=lno1abc", amount = 21_000L, payerNote = "nostr:nipB1:deadbeef")
assertEquals(NwcMethod.PAY, request.method)
assertEquals("bitcoin:?lno=lno1abc", request.params?.payment)
assertEquals(21_000L, request.params?.amount)
assertEquals("nostr:nipXX:deadbeef", request.params?.payer_note)
assertEquals("nostr:nipB1:deadbeef", request.params?.payer_note)
}
@Test
fun testPaySerialization() {
val request = PayMethod.create("bitcoin:?lno=lno1abc", amount = 21_000L, payerNote = "nostr:nipXX:deadbeef")
val request = PayMethod.create("bitcoin:?lno=lno1abc", amount = 21_000L, payerNote = "nostr:nipB1:deadbeef")
val json = OptimizedJsonMapper.toJson(request)
assertTrue(json.contains("\"method\":\"pay\""))
assertTrue(json.contains("\"payment\":\"bitcoin:?lno=lno1abc\""))
assertTrue(json.contains("\"amount\":21000"))
assertTrue(json.contains("\"payer_note\":\"nostr:nipXX:deadbeef\""))
assertTrue(json.contains("\"payer_note\":\"nostr:nipB1:deadbeef\""))
}
@Test
fun testPayDeserialization() {
val json = """{"method":"pay","params":{"payment":"bitcoin:?lno=lno1abc","amount":21000,"payer_note":"nostr:nipXX:deadbeef"}}"""
val json = """{"method":"pay","params":{"payment":"bitcoin:?lno=lno1abc","amount":21000,"payer_note":"nostr:nipB1:deadbeef"}}"""
val request = OptimizedJsonMapper.fromJsonTo<Request>(json)
assertIs<PayMethod>(request)
assertEquals("bitcoin:?lno=lno1abc", request.params?.payment)
assertEquals(21_000L, request.params?.amount)
assertEquals("nostr:nipXX:deadbeef", request.params?.payer_note)
assertEquals("nostr:nipB1:deadbeef", request.params?.payer_note)
}
@Test
@@ -18,15 +18,15 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps
package com.vitorpamplona.quartz.nipB1Bolt12Zaps
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
import kotlin.test.Test
import kotlin.test.assertContentEquals
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01Crypto
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12
import com.vitorpamplona.quartz.utils.Hex
import kotlin.test.Test
@@ -18,13 +18,13 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.builder
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.builder
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ProofFixture
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipXXBolt12Zaps.verify.Bolt12ZapValidator
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ProofFixture
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidator
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -18,12 +18,12 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.offer
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -18,11 +18,11 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import com.vitorpamplona.quartz.TestResourceLoader
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12ProofBuilder
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12ProofBuilder
import com.vitorpamplona.quartz.utils.Hex
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
@@ -18,17 +18,17 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01Crypto
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12ProofBuilder
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Values
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvStream
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Bech32
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Offer
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12PayerProof
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12ProofBuilder
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.Bolt12Values
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.TlvRecord
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.bolt12.TlvStream
import com.vitorpamplona.quartz.utils.sha256.sha256
/**
@@ -18,13 +18,13 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify
package com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nipXXBolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipXXBolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.intent.Bolt12ZapIntentEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.sha256.sha256
import kotlinx.coroutines.test.runTest