Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
add BOLT 12 offer protocol (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
aparkersquare authored Jul 9, 2024
1 parent 0062324 commit a919c58
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/main/kotlin/xyz/block/moneyaddress/Protocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ open class Protocol(open val scheme: String) {
when (this) {
ETHEREUM.scheme -> ETHEREUM
LIGHTNING_ADDRESS.scheme -> LIGHTNING_ADDRESS
LIGHTNING_OFFER.scheme -> LIGHTNING_OFFER
MOBILE_MONEY.scheme -> MOBILE_MONEY
ONCHAIN_ADDRESS.scheme -> ONCHAIN_ADDRESS
SILENT_PAYMENT_ADDRESS.scheme -> SILENT_PAYMENT_ADDRESS
Expand All @@ -31,6 +32,7 @@ open class Protocol(open val scheme: String) {

data object ETHEREUM : Protocol("eth")
data object LIGHTNING_ADDRESS : Protocol("lnaddr")
data object LIGHTNING_OFFER : Protocol("lno")
data object MOBILE_MONEY : Protocol("momo")
data object ONCHAIN_ADDRESS : Protocol("addr")
data object SILENT_PAYMENT_ADDRESS : Protocol("spaddr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import xyz.block.moneyaddress.Currency.Companion.asCurrency
import xyz.block.moneyaddress.Protocol.Companion.asProtocol
import xyz.block.moneyaddress.typed.BtcLightningAddress
import xyz.block.moneyaddress.typed.BtcLightningOffer
import xyz.block.moneyaddress.typed.BtcOnChainAddress
import xyz.block.moneyaddress.typed.MobileMoneyAddress
import xyz.block.moneyaddress.MoneyAddress as UntypedMoneyAddress
Expand Down Expand Up @@ -68,6 +69,7 @@ class TypedMoneyAddressRegistry {

init {
BtcLightningAddress.register(defaultTypedMoneyAddressRegistry)
BtcLightningOffer.register(defaultTypedMoneyAddressRegistry)
BtcOnChainAddress.register(defaultTypedMoneyAddressRegistry)
MobileMoneyAddress.register(defaultTypedMoneyAddressRegistry)
}
Expand Down
44 changes: 44 additions & 0 deletions src/main/kotlin/xyz/block/moneyaddress/typed/BtcLightningOffer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package xyz.block.moneyaddress.typed

import xyz.block.moneyaddress.BTC
import xyz.block.moneyaddress.LIGHTNING_OFFER
import xyz.block.moneyaddress.TypedMoneyAddressRegistry
import xyz.block.moneyaddress.TypedMoneyAddress
import xyz.block.moneyaddress.matches
import xyz.block.moneyaddress.MoneyAddress as UntypedMoneyAddress

/**
* A typed representation of a Bitcoin Lightning Offer
* i.e. a re-usable payment code from [BOLT 12](https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md)
*
* @property address - the string representation of the lightning offer. Not validated.
* @property currency - always [BTC].
* @property protocol - always [LIGHTNING_OFFER].
*/
data class BtcLightningOffer(
override val address: String,
override val id: String,
) : TypedMoneyAddress<String>(address, BTC, LIGHTNING_OFFER, id) {
companion object {
fun register(typedMoneyAddressRegistry: TypedMoneyAddressRegistry) {
typedMoneyAddressRegistry.register(BTC, LIGHTNING_OFFER) { address -> from(address) }
}

/**
* Converts an [UntypedMoneyAddress] to a [BtcLightningAddress].
*
* @throws NotABtcLightningAddressException if the currency is not [BTC] or the protocol is not [LIGHTNING_OFFER].
*/
fun from(untypedMoneyAddress: UntypedMoneyAddress): BtcLightningOffer {
if (!untypedMoneyAddress.matches(BTC, LIGHTNING_OFFER)) {
throw NotABtcLightningOfferException(untypedMoneyAddress)
}
return BtcLightningOffer(untypedMoneyAddress.pss, untypedMoneyAddress.id)
}

fun UntypedMoneyAddress.toBtcLightningOffer(): BtcLightningOffer = from(this)
}
}

class NotABtcLightningOfferException(moneyAddress: UntypedMoneyAddress) :
Throwable("Not a bitcoin lightning offer [$moneyAddress]")
12 changes: 8 additions & 4 deletions src/test/kotlin/xyz/block/moneyaddress/FilterTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package xyz.block.moneyaddress

import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningAddressMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningAddressMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningOfferMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningOfferMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcOnChainMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcOnChainMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.manyMoneyAddresses
Expand All @@ -19,10 +21,12 @@ class FilterTest {
val bitcoinAddresses = manyMoneyAddresses.filter { it.hasCurrency(BTC) }
assertEquals(
listOf(
btcLightningAddressMoneyAddress1,
btcLightningAddressMoneyAddress2,
btcLightningOfferMoneyAddress1,
btcLightningOfferMoneyAddress2,
btcOnChainMoneyAddress1,
btcOnChainMoneyAddress2,
btcLightningMoneyAddress1,
btcLightningMoneyAddress2,
),
bitcoinAddresses
)
Expand Down
20 changes: 12 additions & 8 deletions src/test/kotlin/xyz/block/moneyaddress/MoneyAddressExamples.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,32 @@ import java.util.UUID
class MoneyAddressExamples {

companion object {
val btcLightningAddressMoneyAddress1 = makeMoneyAddress("btc", "lnaddr")
val btcLightningAddressMoneyAddress2 = makeMoneyAddress("btc", "lnaddr")
val btcLightningOfferMoneyAddress1 = makeMoneyAddress("btc", "lno")
val btcLightningOfferMoneyAddress2 = makeMoneyAddress("btc", "lno")
val btcOnChainMoneyAddress1 = makeMoneyAddress("btc", "addr")
val btcOnChainMoneyAddress2 = makeMoneyAddress("btc", "addr")
val btcLightningMoneyAddress1 = makeMoneyAddress("btc", "lnaddr")
val btcLightningMoneyAddress2 = makeMoneyAddress("btc", "lnaddr")
val usdcEthMoneyAddress1 = makeMoneyAddress("usdc", "eth")
val usdcEthMoneyAddress2 = makeMoneyAddress("usdc", "eth")
val kesMomoMoneyAddress1 = makeMoneyAddress("kes", "momo", "mpesa:11111")
val kesMomoMoneyAddress2 = makeMoneyAddress("kes", "momo", "mpesa:22222")
val usdcEthMoneyAddress1 = makeMoneyAddress("usdc", "eth")
val usdcEthMoneyAddress2 = makeMoneyAddress("usdc", "eth")
val zarMomoMoneyAddress1 = makeMoneyAddress("zar", "momo", "mpesa:33333")
val zarMomoMoneyAddress2 = makeMoneyAddress("zar", "momo", "mpesa:44444")
val zzzUnrecognizedMoneyAddress1 = makeMoneyAddress("zzz", "zzz", "zzz1")
val zzzUnrecognizedMoneyAddress2 = makeMoneyAddress("zzz", "zzz", "zzz2")

val manyMoneyAddresses = listOf(
btcLightningAddressMoneyAddress1,
btcLightningAddressMoneyAddress2,
btcLightningOfferMoneyAddress1,
btcLightningOfferMoneyAddress2,
btcOnChainMoneyAddress1,
btcOnChainMoneyAddress2,
btcLightningMoneyAddress1,
btcLightningMoneyAddress2,
usdcEthMoneyAddress1,
usdcEthMoneyAddress2,
kesMomoMoneyAddress1,
kesMomoMoneyAddress2,
usdcEthMoneyAddress1,
usdcEthMoneyAddress2,
zarMomoMoneyAddress1,
zarMomoMoneyAddress2,
zzzUnrecognizedMoneyAddress1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package xyz.block.moneyaddress

import xyz.block.moneyaddress.Currency.Companion.asCurrency
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningAddressMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningAddressMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningOfferMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningOfferMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcOnChainMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcOnChainMoneyAddress2
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.kesMomoMoneyAddress1
Expand All @@ -13,6 +15,7 @@ import xyz.block.moneyaddress.TypedMoneyAddressRegistry.Companion.defaultTypedMo
import xyz.block.moneyaddress.TypedMoneyAddressRegistry.Companion.toTypedMoneyAddress
import xyz.block.moneyaddress.Protocol.Companion.asProtocol
import xyz.block.moneyaddress.typed.BtcLightningAddress
import xyz.block.moneyaddress.typed.BtcLightningOffer
import xyz.block.moneyaddress.typed.BtcOnChainAddress
import xyz.block.moneyaddress.typed.MPESA
import xyz.block.moneyaddress.typed.MobileMoneyAddress
Expand All @@ -25,10 +28,10 @@ class TypedTypedMoneyAddressRegistryTest {

@Test
fun toTypedAddress() {
val btcLightningAddress = btcLightningMoneyAddress1.toTypedMoneyAddress()
val btcLightningAddress = btcLightningAddressMoneyAddress1.toTypedMoneyAddress()
assertIs<BtcLightningAddress>(btcLightningAddress)
assertEquals(
BtcLightningAddress(btcLightningMoneyAddress1.pss, btcLightningMoneyAddress1.id),
BtcLightningAddress(btcLightningAddressMoneyAddress1.pss, btcLightningAddressMoneyAddress1.id),
btcLightningAddress
)

Expand All @@ -43,7 +46,7 @@ class TypedTypedMoneyAddressRegistryTest {
@Test
fun toTypedAddressWithDefaultRegistry() {
assertIs<BtcLightningAddress>(
btcLightningMoneyAddress1.toTypedMoneyAddress(defaultTypedMoneyAddressRegistry)
btcLightningAddressMoneyAddress1.toTypedMoneyAddress(defaultTypedMoneyAddressRegistry)
)
assertIs<MobileMoneyAddress>(
kesMomoMoneyAddress1.toTypedMoneyAddress(defaultTypedMoneyAddressRegistry)
Expand Down Expand Up @@ -71,10 +74,12 @@ class TypedTypedMoneyAddressRegistryTest {
manyMoneyAddresses.map { it.toTypedMoneyAddress() }.filter { it.currency == BTC }
assertEquals(
listOf(
BtcLightningAddress(btcLightningAddressMoneyAddress1.pss, btcLightningAddressMoneyAddress1.id),
BtcLightningAddress(btcLightningAddressMoneyAddress2.pss, btcLightningAddressMoneyAddress2.id),
BtcLightningOffer(btcLightningOfferMoneyAddress1.pss, btcLightningOfferMoneyAddress1.id),
BtcLightningOffer(btcLightningOfferMoneyAddress2.pss, btcLightningOfferMoneyAddress2.id),
BtcOnChainAddress(btcOnChainMoneyAddress1.pss, btcOnChainMoneyAddress1.id),
BtcOnChainAddress(btcOnChainMoneyAddress2.pss, btcOnChainMoneyAddress2.id),
BtcLightningAddress(btcLightningMoneyAddress1.pss, btcLightningMoneyAddress1.id),
BtcLightningAddress(btcLightningMoneyAddress2.pss, btcLightningMoneyAddress2.id),
),
bitcoinAddresses
)
Expand All @@ -97,8 +102,8 @@ class TypedTypedMoneyAddressRegistryTest {
fun toTypedAddressWithFilteringByIsInstance() {
assertEquals(
listOf(
BtcLightningAddress(btcLightningMoneyAddress1.pss, btcLightningMoneyAddress1.id),
BtcLightningAddress(btcLightningMoneyAddress2.pss, btcLightningMoneyAddress2.id),
BtcLightningAddress(btcLightningAddressMoneyAddress1.pss, btcLightningAddressMoneyAddress1.id),
BtcLightningAddress(btcLightningAddressMoneyAddress2.pss, btcLightningAddressMoneyAddress2.id),
),
manyMoneyAddresses.map { it.toTypedMoneyAddress() }.filterIsInstance<BtcLightningAddress>()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package xyz.block.moneyaddress.typed

import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningMoneyAddress1
import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningAddressMoneyAddress1
import xyz.block.moneyaddress.typed.BtcLightningAddress.Companion.toBtcLightningAddress
import kotlin.test.Test
import kotlin.test.assertEquals
Expand All @@ -9,15 +9,15 @@ class BtcLightningAddressTest {
@Test
fun toBtcLightningAddress() {
assertEquals(
BtcLightningAddress(btcLightningMoneyAddress1.pss, btcLightningMoneyAddress1.id),
btcLightningMoneyAddress1.toBtcLightningAddress()
BtcLightningAddress(btcLightningAddressMoneyAddress1.pss, btcLightningAddressMoneyAddress1.id),
btcLightningAddressMoneyAddress1.toBtcLightningAddress()
)
}
@Test
fun fromUntypedMoneyAddress() {
assertEquals(
BtcLightningAddress(btcLightningMoneyAddress1.pss, btcLightningMoneyAddress1.id),
BtcLightningAddress.from(btcLightningMoneyAddress1)
BtcLightningAddress(btcLightningAddressMoneyAddress1.pss, btcLightningAddressMoneyAddress1.id),
BtcLightningAddress.from(btcLightningAddressMoneyAddress1)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.block.moneyaddress.typed

import xyz.block.moneyaddress.MoneyAddressExamples.Companion.btcLightningOfferMoneyAddress1
import xyz.block.moneyaddress.typed.BtcLightningOffer.Companion.toBtcLightningOffer
import kotlin.test.Test
import kotlin.test.assertEquals

class BtcLightningOfferTest {
@Test
fun toBtcLightningOffer() {
assertEquals(
BtcLightningOffer(btcLightningOfferMoneyAddress1.pss, btcLightningOfferMoneyAddress1.id),
btcLightningOfferMoneyAddress1.toBtcLightningOffer()
)
}
@Test
fun fromUntypedMoneyOffer() {
assertEquals(
BtcLightningOffer(btcLightningOfferMoneyAddress1.pss, btcLightningOfferMoneyAddress1.id),
BtcLightningOffer.from(btcLightningOfferMoneyAddress1)
)
}
}

0 comments on commit a919c58

Please sign in to comment.