From 32f76de2a894098a504d87e606f6973a58e74d39 Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Thu, 15 Feb 2024 15:46:39 +0100 Subject: [PATCH] Implement support for Unified QRs for Bitcoin --- .../send/prepare/SendPaymentViewModel.kt | 41 +++++++++++-------- .../send/prepare/domain/RecipientType.kt | 2 +- .../wallet/utils/BitcoinPaymentInstruction.kt | 3 +- .../android/wallet/utils/WalletStringUtils.kt | 4 ++ .../wallet/utils/WalletStringUtilsTest.kt | 15 +++++++ 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/SendPaymentViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/SendPaymentViewModel.kt index b473c785d..f5d133dd8 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/SendPaymentViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/SendPaymentViewModel.kt @@ -82,19 +82,11 @@ class SendPaymentViewModel @Inject constructor( val userId = activeAccountStore.activeUserId() try { when (text.parseRecipientType()) { - RecipientType.LnInvoice -> handleLnInvoiceText(userId = userId, text = text) - RecipientType.LnUrl -> handleLnUrlText(userId = userId, text = text) - RecipientType.LnAddress -> handleLightningAddressText(userId = userId, text = text) - RecipientType.LightningUri -> { - val path = text.split(":").last() - when { - path.isLightningAddress() -> handleLightningAddressText(userId = userId, text = path) - path.isLnUrl() -> handleLnUrlText(userId = userId, text = path) - path.isLnInvoice() -> handleLnInvoiceText(userId = userId, text = path) - } - } - RecipientType.BitcoinAddress -> handleBitcoinAddressText(text = text) - null -> Unit + RecipientType.LnInvoice -> handleLnInvoiceText(userId, text) + RecipientType.LnUrl -> handleLnUrlText(userId, text) + RecipientType.LnAddress -> handleLightningAddressText(userId, text) + RecipientType.BitcoinAddress, RecipientType.BitcoinAddressUri -> handleBitcoinText(text = text) + null -> Timber.w("Unknown text type. [text=$text]") } } catch (error: WssException) { Timber.w(error) @@ -161,7 +153,7 @@ class SendPaymentViewModel @Inject constructor( } } - private fun handleBitcoinAddressText(text: String) { + private fun handleBitcoinText(text: String) { val btcInstructions = text.parseBitcoinPaymentInstructions() if (btcInstructions != null) { setEffect( @@ -182,12 +174,29 @@ class SendPaymentViewModel @Inject constructor( isLnInvoice() -> RecipientType.LnInvoice isLnUrl() -> RecipientType.LnUrl isLightningAddress() -> RecipientType.LnAddress - isLightningAddressUri() -> RecipientType.LightningUri - isBitcoinAddress() || isBitcoinAddressUri() -> RecipientType.BitcoinAddress + isLightningAddressUri() -> { + val path = this.split(":").last() + path.parseLightningRecipientType() + } + + isBitcoinAddress() -> RecipientType.BitcoinAddress + isBitcoinAddressUri() -> { + val parsedBitcoinUri = this.parseBitcoinPaymentInstructions() + return parsedBitcoinUri?.lightning?.parseLightningRecipientType() ?: RecipientType.BitcoinAddressUri + } + else -> null } } + private fun String.parseLightningRecipientType(): RecipientType? = + when { + this.isLightningAddress() -> RecipientType.LnAddress + this.isLnUrl() -> RecipientType.LnUrl + this.isLnInvoice() -> RecipientType.LnInvoice + else -> null + } + private fun processProfileData(profileId: String) = viewModelScope.launch { val profileData = withContext(dispatchers.io()) { diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/domain/RecipientType.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/domain/RecipientType.kt index d329d2ae2..48bf2e3c7 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/domain/RecipientType.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/send/prepare/domain/RecipientType.kt @@ -4,6 +4,6 @@ enum class RecipientType { LnInvoice, LnUrl, LnAddress, - LightningUri, BitcoinAddress, + BitcoinAddressUri, } diff --git a/app/src/main/kotlin/net/primal/android/wallet/utils/BitcoinPaymentInstruction.kt b/app/src/main/kotlin/net/primal/android/wallet/utils/BitcoinPaymentInstruction.kt index a4f7dfe12..6fbfb8bda 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/utils/BitcoinPaymentInstruction.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/utils/BitcoinPaymentInstruction.kt @@ -2,8 +2,9 @@ package net.primal.android.wallet.utils data class BitcoinPaymentInstruction( val address: String, + val lightning: String? = null, val amount: String? = null, val label: String? = null, ) { - fun hasParams() = amount != null || label != null + fun hasParams() = amount != null || label != null || lightning != null } diff --git a/app/src/main/kotlin/net/primal/android/wallet/utils/WalletStringUtils.kt b/app/src/main/kotlin/net/primal/android/wallet/utils/WalletStringUtils.kt index 3bde55f90..14c9ab5db 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/utils/WalletStringUtils.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/utils/WalletStringUtils.kt @@ -55,8 +55,12 @@ fun String.parseBitcoinPaymentInstructions(): BitcoinPaymentInstruction? { val labelParam = params?.find { it.startsWith("label") } val label = labelParam?.split("=")?.lastOrNull()?.asUrlDecoded() + val lightningParam = params?.find { it.startsWith("lightning") } + val lightning = lightningParam?.split("=")?.lastOrNull() + BitcoinPaymentInstruction( address = address, + lightning = lightning, amount = amount, label = label, ) diff --git a/app/src/test/kotlin/net/primal/android/wallet/utils/WalletStringUtilsTest.kt b/app/src/test/kotlin/net/primal/android/wallet/utils/WalletStringUtilsTest.kt index d58de297d..35e96f053 100644 --- a/app/src/test/kotlin/net/primal/android/wallet/utils/WalletStringUtilsTest.kt +++ b/app/src/test/kotlin/net/primal/android/wallet/utils/WalletStringUtilsTest.kt @@ -171,4 +171,19 @@ class WalletStringUtilsTest { amount = "1.23", ) } + + @Test + fun parseBitcoinPaymentInstruction_shouldReturnBtcAddressAndLightningAddressAndAmountAndLabelIfInputsAreValid() { + ("bitcoin:bc1q99ygnq68xrvqd9up7vgapnytwmss4am6ytessw" + + "?lightning=$validLnInvoice" + + "&amount=1.234567" + + "&label=This+is+very+long+comment." + ).parseBitcoinPaymentInstructions() shouldBe + BitcoinPaymentInstruction( + address = "bc1q99ygnq68xrvqd9up7vgapnytwmss4am6ytessw", + lightning = validLnInvoice, + amount = "1.234567", + label = "This is very long comment.", + ) + } }