feat(quartz): add NIP-BC onchain zaps (kind 8333)
Implements NIP-BC onchain zap events analogous to NIP-57 lightning zap receipts but for Bitcoin transactions. The recipient's Nostr pubkey is used directly as the internal key of a BIP-341 P2TR output. Mirrors the nip88Polls package structure with a single `zap/` subpackage: - OnchainZapEvent (kind 8333) with profile / event / addressable-event zap builders - Tags: BitcoinTxIdTag (i = bitcoin:tx:<txid>), AmountTag (sats), BlockTag (hash + height), ProofTag (raw-tx + merkle proof) for optional SPV verification - TagArrayExt / TagArrayBuilderExt for parsing and assembly - EventFactory wired so kind 8333 deserializes into OnchainZapEvent Reuses standard ETag/ATag/PTag/KindTag from nip01Core. Does not yet implement client-side transaction verification or PSBT signer methods.
This commit is contained in:
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.toATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.toETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/**
|
||||
* NIP-BC: Onchain Zaps.
|
||||
*
|
||||
* Kind 8333 event that attributes a Bitcoin onchain payment to a Nostr event or profile.
|
||||
* The recipient's Nostr pubkey is used directly as the internal key of a BIP-341 P2TR
|
||||
* output, so every Nostr pubkey has exactly one corresponding mainnet Taproot address.
|
||||
*
|
||||
* Mainnet only.
|
||||
*/
|
||||
@Immutable
|
||||
class OnchainZapEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
EventHintProvider,
|
||||
AddressHintProvider,
|
||||
PubKeyHintProvider {
|
||||
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||
|
||||
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
|
||||
|
||||
override fun eventHints() = tags.mapNotNull(ETag::parseAsHint)
|
||||
|
||||
override fun linkedEventIds() = tags.mapNotNull(ETag::parseId)
|
||||
|
||||
override fun addressHints() = tags.mapNotNull(ATag::parseAsHint)
|
||||
|
||||
override fun linkedAddressIds() = tags.mapNotNull(ATag::parseAddressId)
|
||||
|
||||
/** The Bitcoin transaction id (64-char lowercase hex) parsed from the `i` tag. */
|
||||
fun txid() = tags.txid()
|
||||
|
||||
/** Sender-claimed amount in satoshis. Must be verified against the on-chain transaction. */
|
||||
fun claimedAmountInSats() = tags.amountInSats()
|
||||
|
||||
/** The hex-encoded pubkey of the recipient (the author being paid). */
|
||||
fun recipient() = tags.firstNotNullOfOrNull(PTag::parseKey)
|
||||
|
||||
/** The event being zapped, if any. */
|
||||
fun zappedEvent() = tags.firstNotNullOfOrNull(ETag::parseId)
|
||||
|
||||
/** The addressable event being zapped, if any. */
|
||||
fun zappedAddress() = tags.firstNotNullOfOrNull(ATag::parseAddressId)
|
||||
|
||||
/** Optional block tag with hash + height enabling SPV verification. */
|
||||
fun block() = tags.block()
|
||||
|
||||
/** Optional inline SPV proof (raw tx hex + merkle proof hex). */
|
||||
fun proof() = tags.proof()
|
||||
|
||||
/** True when neither `e` nor `a` is present — the zap targets the recipient's profile. */
|
||||
fun isProfileZap() = zappedEvent() == null && zappedAddress() == null
|
||||
|
||||
companion object {
|
||||
const val KIND = 8333
|
||||
const val ALT_DESCRIPTION = "Onchain Zap"
|
||||
|
||||
/**
|
||||
* Build an onchain zap that targets a specific event.
|
||||
*/
|
||||
fun build(
|
||||
txid: String,
|
||||
recipientPubKey: HexKey,
|
||||
amountInSats: Long,
|
||||
zappedEvent: EventHintBundle<out Event>,
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<OnchainZapEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, content, createdAt) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
txid(txid)
|
||||
recipient(recipientPubKey)
|
||||
amountInSats(amountInSats)
|
||||
if (zappedEvent.event is AddressableEvent) {
|
||||
zappedAddress(zappedEvent.toATag())
|
||||
}
|
||||
zappedEvent(zappedEvent.toETag())
|
||||
zappedKind(zappedEvent.event.kind)
|
||||
initializer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an onchain zap that targets a recipient's profile (no event / address).
|
||||
*/
|
||||
fun buildProfileZap(
|
||||
txid: String,
|
||||
recipientPubKey: HexKey,
|
||||
amountInSats: Long,
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<OnchainZapEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, content, createdAt) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
txid(txid)
|
||||
recipient(recipientPubKey)
|
||||
amountInSats(amountInSats)
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
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.nipBCOnchainZaps.zap.tags.AmountTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.BitcoinTxIdTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.BlockTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.ProofTag
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.txid(txid: String) = addUnique(BitcoinTxIdTag.assemble(txid))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.recipient(recipientPubKey: HexKey) = addUnique(PTag.assemble(recipientPubKey, null))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.amountInSats(amountInSats: Long) = addUnique(AmountTag.assemble(amountInSats))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.zappedEvent(tag: ETag) = addUnique(tag.toTagArray())
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.zappedAddress(tag: ATag) = addUnique(tag.toATagArray())
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.zappedKind(kind: Int) = addUnique(KindTag.assemble(kind))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.block(
|
||||
blockHashHex: String,
|
||||
height: Long,
|
||||
) = addUnique(BlockTag.assemble(blockHashHex, height))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.block(block: BlockTag) = addUnique(block.toTagArray())
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.proof(
|
||||
rawTxHex: String,
|
||||
merkleProofHex: String,
|
||||
) = addUnique(ProofTag.assemble(rawTxHex, merkleProofHex))
|
||||
|
||||
fun TagArrayBuilder<OnchainZapEvent>.proof(proof: ProofTag) = addUnique(proof.toTagArray())
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.AmountTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.BitcoinTxIdTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.BlockTag
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.tags.ProofTag
|
||||
|
||||
fun TagArray.txid() = firstNotNullOfOrNull(BitcoinTxIdTag::parse)
|
||||
|
||||
fun TagArray.amountInSats() = firstNotNullOfOrNull(AmountTag::parse)
|
||||
|
||||
fun TagArray.block() = firstNotNullOfOrNull(BlockTag::parse)
|
||||
|
||||
fun TagArray.proof() = firstNotNullOfOrNull(ProofTag::parse)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class AmountTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "amount"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
fun parse(tag: Array<String>): Long? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return tag[1].toLongOrNull()
|
||||
}
|
||||
|
||||
fun assemble(amountInSats: Long) = arrayOf(TAG_NAME, amountInSats.toString())
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class BitcoinTxIdTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "i"
|
||||
const val PREFIX = "bitcoin:tx:"
|
||||
const val TXID_LENGTH = 64
|
||||
|
||||
fun isTag(tag: Array<String>) =
|
||||
tag.has(1) &&
|
||||
tag[0] == TAG_NAME &&
|
||||
tag[1].startsWith(PREFIX) &&
|
||||
tag[1].length == PREFIX.length + TXID_LENGTH
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
txid: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == assembleScope(txid)
|
||||
|
||||
fun parse(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].startsWith(PREFIX)) { return null }
|
||||
|
||||
val txid = tag[1].substring(PREFIX.length)
|
||||
ensure(txid.length == TXID_LENGTH) { return null }
|
||||
ensure(txid == txid.lowercase()) { return null }
|
||||
ensure(Hex.isHex(txid)) { return null }
|
||||
|
||||
return txid
|
||||
}
|
||||
|
||||
fun parseScope(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].startsWith(PREFIX)) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
fun assembleScope(txid: String) = PREFIX + txid.lowercase()
|
||||
|
||||
fun assemble(txid: String) = arrayOf(TAG_NAME, assembleScope(txid))
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap.tags
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@Immutable
|
||||
data class BlockTag(
|
||||
val blockHashHex: String,
|
||||
val height: Long,
|
||||
) {
|
||||
fun toTagArray() = assemble(blockHashHex, height)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "block"
|
||||
const val BLOCK_HASH_LENGTH = 64
|
||||
|
||||
fun isTag(tag: Array<String>) =
|
||||
tag.has(2) &&
|
||||
tag[0] == TAG_NAME &&
|
||||
tag[1].length == BLOCK_HASH_LENGTH &&
|
||||
tag[2].isNotEmpty()
|
||||
|
||||
fun parse(tag: Array<String>): BlockTag? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].length == BLOCK_HASH_LENGTH) { return null }
|
||||
ensure(Hex.isHex(tag[1])) { return null }
|
||||
ensure(tag[2].isNotEmpty()) { return null }
|
||||
|
||||
val height = tag[2].toLongOrNull() ?: return null
|
||||
ensure(height >= 0) { return null }
|
||||
|
||||
return BlockTag(tag[1].lowercase(), height)
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
blockHashHex: String,
|
||||
height: Long,
|
||||
) = arrayOf(TAG_NAME, blockHashHex.lowercase(), height.toString())
|
||||
|
||||
fun assemble(block: BlockTag) = assemble(block.blockHashHex, block.height)
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* 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.nipBCOnchainZaps.zap.tags
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@Immutable
|
||||
data class ProofTag(
|
||||
val rawTxHex: String,
|
||||
val merkleProofHex: String,
|
||||
) {
|
||||
fun toTagArray() = assemble(rawTxHex, merkleProofHex)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "proof"
|
||||
|
||||
fun isTag(tag: Array<String>) =
|
||||
tag.has(2) &&
|
||||
tag[0] == TAG_NAME &&
|
||||
tag[1].isNotEmpty()
|
||||
|
||||
fun parse(tag: Array<String>): ProofTag? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
ensure(Hex.isHex(tag[1])) { return null }
|
||||
ensure(Hex.isHex(tag[2])) { return null }
|
||||
|
||||
return ProofTag(tag[1].lowercase(), tag[2].lowercase())
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
rawTxHex: String,
|
||||
merkleProofHex: String,
|
||||
) = arrayOf(TAG_NAME, rawTxHex.lowercase(), merkleProofHex.lowercase())
|
||||
|
||||
fun assemble(proof: ProofTag) = assemble(proof.rawTxHex, proof.merkleProofHex)
|
||||
}
|
||||
}
|
||||
@@ -286,6 +286,7 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRenegotiateEvent
|
||||
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
|
||||
import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent
|
||||
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
|
||||
|
||||
@@ -517,6 +518,7 @@ class EventFactory {
|
||||
NIP90EventPublishScheduleResponseEvent.KIND -> NIP90EventPublishScheduleResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90EventPowDelegationRequestEvent.KIND -> NIP90EventPowDelegationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90EventPowDelegationResponseEvent.KIND -> NIP90EventPowDelegationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OnchainZapEvent.KIND -> OnchainZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PaymentTargetsEvent.KIND -> PaymentTargetsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
Reference in New Issue
Block a user