From 25c530d4de3dea7977818254029ad6c1700805a4 Mon Sep 17 00:00:00 2001 From: Robbie Hanson <304604+robbiehanson@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:41:57 -0300 Subject: [PATCH] If payment is sent to a lightningAddress, this information is now properly stored in the payment_metadata table --- .../kotlin/KotlinExtensions+Payments.swift | 15 ++++- .../officers/BusinessManager.swift | 6 ++ .../phoenix-ios/views/send/ValidateView.swift | 26 ++++--- .../views/transactions/PaymentCell.swift | 7 +- .../fr.acinq.phoenix/data/WalletPayment.kt | 2 + .../db/payments/MetadataQueries.kt | 54 +++++---------- .../db/payments/MetadataTypes.kt | 4 ++ .../managers/ContactsManager.kt | 39 ++++++----- .../managers/PaymentsFetcher.kt | 2 +- .../managers/PaymentsManager.kt | 21 +++--- .../fr.acinq.phoenix/managers/SendManager.kt | 67 +++++++++++++++++-- .../fr.acinq.phoenix.db/PaymentsMetadata.sq | 18 ++--- .../fr.acinq.phoenix.db/migrations/10.sqm | 7 ++ .../fr/acinq/phoenix/db/CloudKitPaymentsDb.kt | 3 +- .../acinq/phoenix/utils/LightningExposure.kt | 36 +++------- 15 files changed, 176 insertions(+), 131 deletions(-) create mode 100644 phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/migrations/10.sqm diff --git a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift index 9b684d0cf..57f8de446 100644 --- a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift +++ b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift @@ -210,7 +210,19 @@ extension WalletPaymentInfo { func addToContactsInfo() -> AddToContactsInfo? { if payment is Lightning_kmpOutgoingPayment { - // Todo: check for lightning address (requires db change in metadata table) + + // First check for a lightning address. + // Remember that an outgoing payment might have both an address & offer (i.e. BIP-353). + // But from the user's perspective, they sent a payment to the address. + // The fact that it used an offer under-the-hood is just a technicality. + // What they expect to save is the lightning address. + // + // Note: in the future we may support something like "offer pinning" for an LN address. + // But that's a different feature. The user's perspective remains the same. + // + if let address = self.metadata.lightningAddress { + return AddToContactsInfo(offer: nil, address: address) + } let invoiceRequest = payment.outgoingInvoiceRequest() if let offer = invoiceRequest?.offer { @@ -230,6 +242,7 @@ extension WalletPaymentMetadata { originalFiat: nil, userDescription: nil, userNotes: nil, + lightningAddress: nil, modifiedAt: nil ) } diff --git a/phoenix-ios/phoenix-ios/officers/BusinessManager.swift b/phoenix-ios/phoenix-ios/officers/BusinessManager.swift index 3993cca8d..6c22cc24a 100644 --- a/phoenix-ios/phoenix-ios/officers/BusinessManager.swift +++ b/phoenix-ios/phoenix-ios/officers/BusinessManager.swift @@ -106,6 +106,12 @@ class BusinessManager { }.store(in: &appCancellables) WatchTower.shared.prepare() + + #if DEBUG + if let path = PlatformIosKt.getDatabaseFilesDirectoryPath(ctx: PlatformContext.default) { + log.debug("DB path: \(path)") + } + #endif } // -------------------------------------------------- diff --git a/phoenix-ios/phoenix-ios/views/send/ValidateView.swift b/phoenix-ios/phoenix-ios/views/send/ValidateView.swift index cfdb6b840..2c2dc4e8c 100644 --- a/phoenix-ios/phoenix-ios/views/send/ValidateView.swift +++ b/phoenix-ios/phoenix-ios/views/send/ValidateView.swift @@ -1809,10 +1809,6 @@ struct ValidateView: View { log.warning("ignore: payment already in progress") return } - guard let peer = Biz.business.peerManager.peerStateValue() else { - log.warning("ignore: peer == nil") - return - } paymentInProgress = true payOfferProblem = nil @@ -1832,14 +1828,16 @@ struct ValidateView: View { payerKey = Lightning_randomKey() } - let response: Lightning_kmpOfferNotPaid? = try await peer.betterPayOffer( - paymentId: paymentId, - amount: Lightning_kmpMilliSatoshi(msat: msat), - offer: model.offer, - payerKey: payerKey, - payerNote: payerNote, - fetchInvoiceTimeoutInSeconds: 30 - ) + let response: Lightning_kmpOfferNotPaid? = + try await Biz.business.sendManager._payBolt12Offer( + paymentId: paymentId, + amount: Lightning_kmpMilliSatoshi(msat: msat), + offer: model.offer, + lightningAddress: model.lightningAddress, + payerKey: payerKey, + payerNote: payerNote, + fetchInvoiceTimeoutInSeconds: 30 + ) paymentInProgress = false @@ -1992,7 +1990,7 @@ struct ValidateView: View { do { let result1: Bitcoin_kmpEither = try await Biz.business.sendManager.lnurlPay_requestInvoice( - paymentIntent: model.paymentIntent, + pay: model, amount: updatedMsat, comment: commentSnapshot ) @@ -2014,7 +2012,7 @@ struct ValidateView: View { let invoice: LnurlPay.Invoice = result1.right! try await Biz.business.sendManager.lnurlPay_payInvoice( - paymentIntent: model.paymentIntent, + pay: model, amount: updatedMsat, comment: commentSnapshot, invoice: invoice, diff --git a/phoenix-ios/phoenix-ios/views/transactions/PaymentCell.swift b/phoenix-ios/phoenix-ios/views/transactions/PaymentCell.swift index 9a79ba86b..b0a4c00b5 100644 --- a/phoenix-ios/phoenix-ios/views/transactions/PaymentCell.swift +++ b/phoenix-ios/phoenix-ios/views/transactions/PaymentCell.swift @@ -10,11 +10,8 @@ fileprivate var log = LoggerFactory.shared.logger(filename, .warning) struct PaymentCell : View { - static let fetchOptions = WalletPaymentFetchOptions.companion.Descriptions.plus( - other: WalletPaymentFetchOptions.companion.OriginalFiat - ).plus( - other: WalletPaymentFetchOptions.companion.Contact - ) + static let fetchOptions = WalletPaymentFetchOptions.companion.Common + // ^ Descriptions + OriginalFiat + Contacts private let paymentsManager = Biz.business.paymentsManager diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/WalletPayment.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/WalletPayment.kt index 04a2eb821..054198288 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/WalletPayment.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/WalletPayment.kt @@ -165,6 +165,7 @@ data class WalletPaymentMetadata( val originalFiat: ExchangeRate.BitcoinPriceRate? = null, val userDescription: String? = null, val userNotes: String? = null, + val lightningAddress: String? = null, val modifiedAt: Long? = null ) @@ -211,6 +212,7 @@ data class WalletPaymentFetchOptions(val flags: Int) { // <- bitmask val OriginalFiat = WalletPaymentFetchOptions(1 shl 3) val Contact = WalletPaymentFetchOptions(1 shl 4) + val Common = Descriptions + OriginalFiat + Contact val All = Descriptions + Lnurl + UserNotes + OriginalFiat + Contact } } \ No newline at end of file diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataQueries.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataQueries.kt index e33c8562d..e5d3714b0 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataQueries.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataQueries.kt @@ -32,7 +32,8 @@ class MetadataQueries(val database: PaymentsDatabase) { user_notes = data.user_notes, modified_at = data.modified_at, original_fiat_type = data.original_fiat?.first, - original_fiat_rate = data.original_fiat?.second + original_fiat_rate = data.original_fiat?.second, + lightning_address = data.lightning_address ) } @@ -48,11 +49,8 @@ class MetadataQueries(val database: PaymentsDatabase) { WalletPaymentFetchOptions.None -> { null } - WalletPaymentFetchOptions.Descriptions + WalletPaymentFetchOptions.OriginalFiat -> { - getMetadataDescriptionsAndOriginalFiat(id) - } - WalletPaymentFetchOptions.Descriptions -> { - getMetadataDescriptions(id) + WalletPaymentFetchOptions.Common -> { + getMetadataCommon(id) } else -> { getMetadataAll(id) @@ -60,21 +58,11 @@ class MetadataQueries(val database: PaymentsDatabase) { } } - private fun getMetadataDescriptions(id: WalletPaymentId): WalletPaymentMetadata? { - return queries.fetchDescriptions( - type = id.dbType.value, - id = id.dbId, - mapper = ::mapDescriptions - ).executeAsOneOrNull() - } - - private fun getMetadataDescriptionsAndOriginalFiat( - id: WalletPaymentId - ): WalletPaymentMetadata? { - return queries.fetchDescriptionsAndOriginalFiat( + private fun getMetadataCommon(id: WalletPaymentId): WalletPaymentMetadata? { + return queries.fetchCommon( type = id.dbType.value, id = id.dbId, - mapper = ::mapDescriptionsAndOriginalFiat + mapper = ::mapCommon ).executeAsOneOrNull() } @@ -120,7 +108,8 @@ class MetadataQueries(val database: PaymentsDatabase) { user_notes = userNotes, modified_at = modifiedAt, original_fiat_type = null, - original_fiat_rate = null + original_fiat_rate = null, + lightning_address = null ) } didUpdateWalletPaymentMetadata(id, database) @@ -128,27 +117,13 @@ class MetadataQueries(val database: PaymentsDatabase) { } companion object { - fun mapDescriptions( - lnurl_description: String?, - user_description: String?, - modified_at: Long? - ): WalletPaymentMetadata { - val lnurl = if (lnurl_description != null) { - LnurlPayMetadata.placeholder(lnurl_description) - } else null - return WalletPaymentMetadata( - userDescription = user_description, - lnurl = lnurl, - modifiedAt = modified_at - ) - } - - fun mapDescriptionsAndOriginalFiat( + fun mapCommon( lnurl_description: String?, user_description: String?, modified_at: Long?, original_fiat_type: String?, - original_fiat_rate: Double? + original_fiat_rate: Double?, + lightning_address: String? ): WalletPaymentMetadata { val lnurl = if (lnurl_description != null) { LnurlPayMetadata.placeholder(lnurl_description) @@ -170,6 +145,7 @@ class MetadataQueries(val database: PaymentsDatabase) { lnurl = lnurl, originalFiat = originalFiat, userDescription = user_description, + lightningAddress = lightning_address, modifiedAt = modified_at ) } @@ -189,7 +165,8 @@ class MetadataQueries(val database: PaymentsDatabase) { user_notes: String?, modified_at: Long?, original_fiat_type: String?, - original_fiat_rate: Double? + original_fiat_rate: Double?, + lightning_address: String? ): WalletPaymentMetadata { val lnurlBase = if (lnurl_base_type != null && lnurl_base_blob != null) { @@ -219,6 +196,7 @@ class MetadataQueries(val database: PaymentsDatabase) { original_fiat = originalFiat, user_description = user_description, user_notes = user_notes, + lightning_address = lightning_address, modified_at = modified_at ).deserialize() } diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataTypes.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataTypes.kt index 21166ab4c..6910b9cac 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataTypes.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/payments/MetadataTypes.kt @@ -223,6 +223,7 @@ data class WalletPaymentMetadataRow( val original_fiat: Pair? = null, val user_description: String? = null, val user_notes: String? = null, + val lightning_address: String? = null, val modified_at: Long? = null ) { @@ -271,6 +272,7 @@ data class WalletPaymentMetadataRow( originalFiat = originalFiat, userDescription = user_description, userNotes = user_notes, + lightningAddress = lightning_address, modifiedAt = modified_at ) } @@ -286,6 +288,7 @@ data class WalletPaymentMetadataRow( && original_fiat == null && user_description == null && user_notes == null + && lightning_address == null } companion object { @@ -319,6 +322,7 @@ data class WalletPaymentMetadataRow( original_fiat = originalFiat, user_description = metadata.userDescription, user_notes = metadata.userNotes, + lightning_address = metadata.lightningAddress, modified_at = metadata.modifiedAt ) diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/ContactsManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/ContactsManager.kt index 809a5f643..4661e7178 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/ContactsManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/ContactsManager.kt @@ -26,6 +26,7 @@ import fr.acinq.lightning.wire.OfferTypes import fr.acinq.phoenix.PhoenixBusiness import fr.acinq.phoenix.data.ContactAddress import fr.acinq.phoenix.data.ContactInfo +import fr.acinq.phoenix.data.WalletPaymentInfo import fr.acinq.phoenix.db.SqliteAppDb import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest @@ -122,24 +123,6 @@ class ContactsManager( return contactsMap.value[contactId] } - fun contactIdForPayment(payment: WalletPayment): UUID? { - return if (payment is IncomingPayment) { - payment.incomingOfferMetadata()?.let { offerMetadata -> - publicKeyMap.value[offerMetadata.payerKey] - } - } else { - payment.outgoingInvoiceRequest()?.let {invoiceRequest -> - offerMap.value[invoiceRequest.offer.offerId] - } - } - } - - fun contactForPayment(payment: WalletPayment): ContactInfo? { - return contactIdForPayment(payment)?.let { contactId -> - contactForId(contactId) - } - } - fun contactIdForOfferId(offerId: ByteVector32): UUID? { return offerMap.value[offerId] } @@ -177,4 +160,24 @@ class ContactsManager( contactForId(contactId) } } + + fun contactIdForPaymentInfo(paymentInfo: WalletPaymentInfo): UUID? { + return if (paymentInfo.payment is IncomingPayment) { + paymentInfo.payment.incomingOfferMetadata()?.let { offerMetadata -> + contactIdForPayerPubKey(offerMetadata.payerKey) + } + } else { + paymentInfo.metadata.lightningAddress?.let { address -> + contactIdForLightningAddress(address) + } ?: paymentInfo.payment.outgoingInvoiceRequest()?.let { invoiceRequest -> + contactIdForOfferId(invoiceRequest.offer.offerId) + } + } + } + + fun contactForPaymentInfo(paymentInfo: WalletPaymentInfo): ContactInfo? { + return contactIdForPaymentInfo(paymentInfo)?.let { contactId -> + contactForId(contactId) + } + } } \ No newline at end of file diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsFetcher.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsFetcher.kt index b68efee36..e8cb0232b 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsFetcher.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsFetcher.kt @@ -82,7 +82,7 @@ class PaymentsFetcher( // So we should refresh them everytime. // Note that fetching this information doesn't require a trip to the database. // Everything is already in memory, so the lookup is very fast. - val updatedContact = contactsManager.contactForPayment(paymentInfo.payment) + val updatedContact = contactsManager.contactForPaymentInfo(paymentInfo) paymentInfo.copy(contact = updatedContact) } else { paymentInfo diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsManager.kt index 54c14262d..c2001e623 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsManager.kt @@ -200,17 +200,20 @@ class PaymentsManager( is WalletPaymentId.ChannelCloseOutgoingPaymentId -> paymentsDb().getChannelCloseOutgoingPayment(id.id, options) is WalletPaymentId.SpliceCpfpOutgoingPaymentId -> paymentsDb().getSpliceCpfpOutgoingPayment(id.id, options) is WalletPaymentId.InboundLiquidityOutgoingPaymentId -> paymentsDb().getInboundLiquidityOutgoingPayment(id.id, options) - }?.let { - val payment = it.first - val contact = if (options.contains(WalletPaymentFetchOptions.Contact)) { - contactsManager.contactForPayment(payment) - } else { null } - WalletPaymentInfo( - payment = payment, - metadata = it.second ?: WalletPaymentMetadata(), - contact = contact, + }?.let { pair -> + val paymentInfo = WalletPaymentInfo( + payment = pair.first, + metadata = pair.second ?: WalletPaymentMetadata(), + contact = null, fetchOptions = options ) + if (options.contains(WalletPaymentFetchOptions.Contact)) { + contactsManager.contactForPaymentInfo(paymentInfo)?.let { contact -> + paymentInfo.copy(contact = contact) + } ?: paymentInfo + } else { + paymentInfo + } } } } diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/SendManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/SendManager.kt index 15b776913..846c985cb 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/SendManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/SendManager.kt @@ -2,12 +2,17 @@ package fr.acinq.phoenix.managers import fr.acinq.bitcoin.BitcoinError import fr.acinq.bitcoin.Chain +import fr.acinq.bitcoin.PrivateKey import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.Lightning import fr.acinq.lightning.MilliSatoshi import fr.acinq.lightning.TrampolineFees import fr.acinq.lightning.db.LightningOutgoingPayment +import fr.acinq.lightning.io.OfferInvoiceReceived +import fr.acinq.lightning.io.OfferNotPaid import fr.acinq.lightning.io.PayInvoice +import fr.acinq.lightning.io.PayOffer +import fr.acinq.lightning.io.Peer import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.debug import fr.acinq.lightning.logging.error @@ -31,12 +36,15 @@ import fr.acinq.phoenix.utils.DnsResolvers import fr.acinq.phoenix.utils.EmailLikeAddress import fr.acinq.phoenix.utils.Parser import io.ktor.http.Url +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.booleanOrNull @@ -456,6 +464,50 @@ class SendManager( ) } + suspend fun payBolt12Offer( + paymentId: UUID, + amount: MilliSatoshi, + offer: OfferTypes.Offer, + lightningAddress: String?, + payerKey: PrivateKey, + payerNote: String?, + fetchInvoiceTimeout: Duration + ): OfferNotPaid? { + val peer = peerManager.getPeer() + + lightningAddress?.let { + val metadata = WalletPaymentMetadata(lightningAddress = it) + WalletPaymentMetadataRow.serialize(metadata)?.let { row -> + databaseManager.paymentsDb().enqueueMetadata( + row = row, + id = WalletPaymentId.LightningOutgoingPaymentId(paymentId) + ) + } + } + + val res = CompletableDeferred() + launch { + peer.eventsFlow.collect { + if (it is OfferNotPaid && it.request.paymentId == paymentId) { + res.complete(it) + cancel() + } else if (it is OfferInvoiceReceived && it.request.paymentId == paymentId) { + res.complete(null) + cancel() + } + } + } + peer.send(PayOffer( + paymentId = paymentId, + payerKey = payerKey, + payerNote = payerNote, + amount = amount, + offer = offer, + fetchInvoiceTimeout = fetchInvoiceTimeout + )) + return res.await() + } + /** * Step 1 of 2: * First call this function to convert the LnurlPay.Intent into a LnurlPay.Invoice. @@ -463,12 +515,12 @@ class SendManager( * Note: This step is cancellable. The UI can simply ignore the result. */ suspend fun lnurlPay_requestInvoice( - paymentIntent: LnurlPay.Intent, + pay: ParseResult.Lnurl.Pay, amount: MilliSatoshi, comment: String? ): Either { val task = lnurlManager.requestPayInvoice( - intent = paymentIntent, + intent = pay.paymentIntent, amount = amount, comment = comment ) @@ -494,7 +546,7 @@ class SendManager( else -> Either.Left( LnurlPayError.RemoteError( LnurlError.RemoteFailure.Unreadable( - origin = paymentIntent.callback.host + origin = pay.paymentIntent.callback.host ) ) ) @@ -509,7 +561,7 @@ class SendManager( * Note: This step is non-cancellable. */ suspend fun lnurlPay_payInvoice( - paymentIntent: LnurlPay.Intent, + pay: ParseResult.Lnurl.Pay, amount: MilliSatoshi, comment: String?, invoice: LnurlPay.Invoice, @@ -521,11 +573,12 @@ class SendManager( invoice = invoice.invoice, metadata = WalletPaymentMetadata( lnurl = LnurlPayMetadata( - pay = paymentIntent, - description = paymentIntent.metadata.plainText, + pay = pay.paymentIntent, + description = pay.paymentIntent.metadata.plainText, successAction = invoice.successAction ), - userNotes = comment + userNotes = comment, + lightningAddress = pay.lightningAddress ) ) } diff --git a/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/PaymentsMetadata.sq b/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/PaymentsMetadata.sq index 815de3594..a42292498 100644 --- a/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/PaymentsMetadata.sq +++ b/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/PaymentsMetadata.sq @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS payments_metadata ( modified_at INTEGER DEFAULT NULL, original_fiat_type TEXT DEFAULT NULL, original_fiat_rate REAL DEFAULT NULL, + lightning_address TEXT DEFAULT NULL, PRIMARY KEY (type, id) ); @@ -47,8 +48,9 @@ INSERT INTO payments_metadata ( lnurl_successAction_type, lnurl_successAction_blob, user_description, user_notes, modified_at, - original_fiat_type, original_fiat_rate) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + original_fiat_type, original_fiat_rate, + lightning_address) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); updateUserInfo: UPDATE payments_metadata @@ -57,19 +59,13 @@ SET user_description = ?, modified_at = ? WHERE type = ? AND id = ?; -fetchDescriptions: -SELECT lnurl_description, - user_description, - modified_at -FROM payments_metadata -WHERE type = ? AND id = ?; - -fetchDescriptionsAndOriginalFiat: +fetchCommon: SELECT lnurl_description, user_description, modified_at, original_fiat_type, - original_fiat_rate + original_fiat_rate, + lightning_address FROM payments_metadata WHERE type = ? AND id = ?; diff --git a/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/migrations/10.sqm b/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/migrations/10.sqm new file mode 100644 index 000000000..450376674 --- /dev/null +++ b/phoenix-shared/src/commonMain/paymentsdb/fr.acinq.phoenix.db/migrations/10.sqm @@ -0,0 +1,7 @@ +-- Migration: v10 -> v11 +-- +-- Changes: +-- * Added a new column [lightning_address] in table [payments_metadata] + +ALTER TABLE payments_metadata + ADD COLUMN lightning_address TEXT DEFAULT NULL; diff --git a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/db/CloudKitPaymentsDb.kt b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/db/CloudKitPaymentsDb.kt index 78727f096..5f74d7300 100644 --- a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/db/CloudKitPaymentsDb.kt +++ b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/db/CloudKitPaymentsDb.kt @@ -653,7 +653,8 @@ class CloudKitPaymentsDb( user_notes = row.user_notes, modified_at = row.modified_at, original_fiat_type = row.original_fiat?.first, - original_fiat_rate = row.original_fiat?.second + original_fiat_rate = row.original_fiat?.second, + lightning_address = row.lightning_address ) } } // diff --git a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt index f3a1c8daf..030f47885 100644 --- a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt +++ b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt @@ -36,15 +36,12 @@ import fr.acinq.lightning.crypto.KeyManager import fr.acinq.lightning.db.IncomingPayment import fr.acinq.lightning.db.LightningOutgoingPayment import fr.acinq.lightning.io.NativeSocketException -import fr.acinq.lightning.io.OfferInvoiceReceived import fr.acinq.lightning.io.OfferNotPaid import fr.acinq.lightning.io.PaymentNotSent import fr.acinq.lightning.io.PaymentProgress import fr.acinq.lightning.io.PaymentSent -import fr.acinq.lightning.io.PayOffer import fr.acinq.lightning.io.Peer import fr.acinq.lightning.io.PeerEvent -import fr.acinq.lightning.io.SendPaymentResult import fr.acinq.lightning.io.TcpSocket import fr.acinq.lightning.payment.FinalFailure import fr.acinq.lightning.payment.LiquidityPolicy @@ -57,13 +54,9 @@ import fr.acinq.lightning.utils.toByteArray import fr.acinq.lightning.utils.toNSData import fr.acinq.lightning.wire.LiquidityAds import fr.acinq.lightning.wire.OfferTypes -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance +import fr.acinq.phoenix.managers.SendManager import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import platform.Foundation.NSData import kotlin.time.Duration.Companion.seconds @@ -381,33 +374,24 @@ suspend fun Peer.fundingRate(amount: Satoshi): LiquidityAds.FundingRate? { return this.remoteFundingRates.filterNotNull().first().findRate(amount) } -suspend fun Peer.betterPayOffer( +// kotlinx.datetime.Duration isn't properly exposed to iOS. +// So we need this little workaround until that issue is fixed. +suspend fun SendManager._payBolt12Offer( paymentId: UUID, amount: MilliSatoshi, offer: OfferTypes.Offer, + lightningAddress: String?, payerKey: PrivateKey, payerNote: String?, fetchInvoiceTimeoutInSeconds: Int ): OfferNotPaid? { - val res = CompletableDeferred() - launch { - eventsFlow.collect { - if (it is OfferNotPaid && it.request.paymentId == paymentId) { - res.complete(it) - cancel() - } else if (it is OfferInvoiceReceived && it.request.paymentId == paymentId) { - res.complete(null) - cancel() - } - } - } - send(PayOffer( + return payBolt12Offer( paymentId = paymentId, - payerKey = payerKey, - payerNote = payerNote, amount = amount, offer = offer, + lightningAddress = lightningAddress, + payerKey = payerKey, + payerNote = payerNote, fetchInvoiceTimeout = fetchInvoiceTimeoutInSeconds.seconds - )) - return res.await() + ) }