diff --git a/app/src/main/java/to/bitkit/env/Env.kt b/app/src/main/java/to/bitkit/env/Env.kt index e3b4b7602..3a1c0bf63 100644 --- a/app/src/main/java/to/bitkit/env/Env.kt +++ b/app/src/main/java/to/bitkit/env/Env.kt @@ -245,6 +245,9 @@ internal object Env { @Suppress("ConstPropertyName") object Defaults { + /** Default Bolt11 invoice expiry in seconds. */ + const val bolt11InvoiceExpirySeconds = 3_600u + /** Recommended transaction base fee in sats */ const val recommendedBaseFee = 256u diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 59a769335..851a7f4f2 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -348,12 +348,7 @@ class ActivityRepo @Inject constructor( getActivities( filter = ActivityFilter.ALL, sortDirection = SortDirection.DESC, - ).getOrThrow().filter { activity -> - when (activity) { - is Activity.Lightning -> PubkyPublicKeyFormat.matches(activity.v1.contact, normalizedKey) - is Activity.Onchain -> PubkyPublicKeyFormat.matches(activity.v1.contact, normalizedKey) - } - } + ).getOrThrow().filter { PubkyPublicKeyFormat.matches(it.contact(), normalizedKey) } }.onFailure { Logger.error("Failed to load contact activities for '$publicKey'", it, context = TAG) } diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index 43f123ab4..d5c7b44d4 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -41,6 +41,7 @@ import org.lightningdevkit.ldknode.ChannelDetails import to.bitkit.async.ServiceQueue import to.bitkit.data.CacheStore import to.bitkit.di.BgDispatcher +import to.bitkit.env.Defaults import to.bitkit.env.Env import to.bitkit.ext.calculateRemoteBalance import to.bitkit.ext.nowTimestamp @@ -462,7 +463,7 @@ class BlocktankRepo @Inject constructor( val invoice = lightningRepo.createInvoice( amountSats = null, description = "blocktank-gift-code:$code", - expirySeconds = 3600u, + expirySeconds = Defaults.bolt11InvoiceExpirySeconds, ).getOrThrow() Logger.debug("Created invoice for gift code, requesting payment from LSP", context = TAG) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 76bc4f89c..840bed142 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -58,6 +58,7 @@ import to.bitkit.data.SettingsStore import to.bitkit.data.backup.VssBackupClientLdk import to.bitkit.data.keychain.Keychain import to.bitkit.di.BgDispatcher +import to.bitkit.env.Defaults import to.bitkit.env.Env import to.bitkit.ext.getSatsPerVByteFor import to.bitkit.ext.nowTimestamp @@ -917,7 +918,7 @@ class LightningRepo @Inject constructor( suspend fun createInvoice( amountSats: ULong? = null, description: String, - expirySeconds: UInt = 86_400u, + expirySeconds: UInt = Defaults.bolt11InvoiceExpirySeconds, ): Result = executeWhenNodeRunning("createInvoice") { updateGeoBlockState() runCatching { lightningService.receive(amountSats, description, expirySeconds) } @@ -926,7 +927,7 @@ class LightningRepo @Inject constructor( suspend fun createInvoiceMsats( amountMsats: ULong, description: String, - expirySeconds: UInt = 86_400u, + expirySeconds: UInt = Defaults.bolt11InvoiceExpirySeconds, ): Result = executeWhenNodeRunning("createInvoiceMsats") { updateGeoBlockState() runCatching { lightningService.receiveMsats(amountMsats, description, expirySeconds) } diff --git a/app/src/main/java/to/bitkit/repositories/PublicPaykitRepo.kt b/app/src/main/java/to/bitkit/repositories/PublicPaykitRepo.kt index bb1e99cb2..8d8d6bd15 100644 --- a/app/src/main/java/to/bitkit/repositories/PublicPaykitRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/PublicPaykitRepo.kt @@ -29,6 +29,7 @@ import kotlin.time.Clock import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes import kotlin.time.ExperimentalTime +import to.bitkit.di.json as appJson sealed class PublicPaykitError(message: String) : AppError(message) { data object InvalidPayload : PublicPaykitError("Invalid Paykit payment endpoint payload") @@ -57,7 +58,6 @@ class PublicPaykitRepo @Inject constructor( ) { companion object { private val methodIdPattern = Regex("^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$") - private val json = Json { ignoreUnknownKeys = true } private val payablePreferenceOrder = listOf( MethodId.Bolt11, @@ -77,7 +77,7 @@ class PublicPaykitRepo @Inject constructor( val knownMethodId = MethodId.fromRawValue(methodId) ?: return null val payload = runCatching { - json.decodeFromString(endpointData) + appJson.decodeFromString(endpointData) }.getOrNull() ?: return null val value = payload.value.trim() if (value.isEmpty()) return null @@ -94,7 +94,7 @@ class PublicPaykitRepo @Inject constructor( fun serializePayload(value: String): String { val trimmedValue = value.trim() if (trimmedValue.isEmpty()) throw PublicPaykitError.InvalidPayload - return json.encodeToString(PaymentEndpointPayload(value = trimmedValue)) + return Json.encodeToString(PaymentEndpointPayload(value = trimmedValue)) } fun paymentRequest(endpoints: List): String { diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index 6fbe4925d..977a928b8 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -46,6 +46,7 @@ import to.bitkit.data.SettingsStore import to.bitkit.data.backup.VssStoreIdProvider import to.bitkit.data.keychain.Keychain import to.bitkit.di.BgDispatcher +import to.bitkit.env.Defaults import to.bitkit.env.Env import to.bitkit.ext.totalNextOutboundHtlcLimitSats import to.bitkit.ext.uByteList @@ -592,11 +593,19 @@ class LightningService @Inject constructor( return true } - suspend fun receive(sat: ULong? = null, description: String, expirySecs: UInt = 3600u): String { + suspend fun receive( + sat: ULong? = null, + description: String, + expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds, + ): String { return receiveMsats(amountMsat = sat?.let { it * 1000u }, description = description, expirySecs = expirySecs) } - suspend fun receiveMsats(amountMsat: ULong? = null, description: String, expirySecs: UInt = 3600u): String { + suspend fun receiveMsats( + amountMsat: ULong? = null, + description: String, + expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds, + ): String { val node = this.node ?: throw ServiceError.NodeNotSetup() val message = description diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityContactTitle.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ContactActivityTitle.kt similarity index 100% rename from app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityContactTitle.kt rename to app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ContactActivityTitle.kt diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt index cfba90279..97099a267 100644 --- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt @@ -1373,8 +1373,10 @@ class AppViewModel @Inject constructor( fun preserveContactPaymentContext(paymentHash: String) { synchronized(contactPaymentContextLock) { - activeContactPaymentContext?.let { - pendingContactPaymentContexts[paymentHash] = it + val context = activeContactPaymentContext + if (context != null) { + pendingContactPaymentContexts[paymentHash] = context + activeContactPaymentContext = null } } } @@ -2069,7 +2071,7 @@ class AppViewModel @Inject constructor( lightningRepo.createInvoiceMsats( amountMsats = lnurl.data.maxWithdrawable, description = lnurl.data.defaultDescription, - expirySeconds = 3600u, + expirySeconds = Defaults.bolt11InvoiceExpirySeconds, ) } else { val withdrawAmountSats = _sendUiState.value.amount.coerceAtLeast( @@ -2079,7 +2081,7 @@ class AppViewModel @Inject constructor( lightningRepo.createInvoice( amountSats = withdrawAmountSats, description = lnurl.data.defaultDescription, - expirySeconds = 3600u, + expirySeconds = Defaults.bolt11InvoiceExpirySeconds, ) }.getOrNull() diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index 4c0fb52f0..f22078a41 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -40,6 +40,7 @@ import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore import to.bitkit.data.backup.VssBackupClientLdk import to.bitkit.data.keychain.Keychain +import to.bitkit.env.Defaults import to.bitkit.ext.createChannelDetails import to.bitkit.ext.of import to.bitkit.models.CoinSelectionPreference @@ -200,11 +201,15 @@ class LightningRepoTest : BaseUnitTest() { lightningService.receive( sat = 100uL, description = "test", - expirySecs = 3600u + expirySecs = Defaults.bolt11InvoiceExpirySeconds, ) ).thenReturn(testInvoice) - val result = sut.createInvoice(amountSats = 100uL, description = "test", expirySeconds = 3600u) + val result = sut.createInvoice( + amountSats = 100uL, + description = "test", + expirySeconds = Defaults.bolt11InvoiceExpirySeconds, + ) assertTrue(result.isSuccess) assertEquals(testInvoice, result.getOrNull()) } diff --git a/app/src/test/java/to/bitkit/repositories/PublicPaykitRepoTest.kt b/app/src/test/java/to/bitkit/repositories/PublicPaykitRepoTest.kt index 4a9d02fee..7b2598ed9 100644 --- a/app/src/test/java/to/bitkit/repositories/PublicPaykitRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/PublicPaykitRepoTest.kt @@ -5,6 +5,7 @@ import com.synonym.bitkitcore.NetworkType import com.synonym.bitkitcore.Scanner import com.synonym.paykit.FfiPaymentEntry import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Before import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.inOrder @@ -29,36 +30,49 @@ import kotlin.time.Instant class PublicPaykitRepoTest : BaseUnitTest() { companion object { private const val NOW_MILLIS = 1_000L + private const val PUBLIC_BOLT11_EXPIRY_SECONDS = 86_400u + } + + private val pubkyRepo = mock() + private val walletRepo = mock() + private val lightningRepo = mock() + private val coreService = mock() + private val settingsStore = mock() + private val clock = mock() + + private val publicKey = MutableStateFlow("pubkyself") + private val walletState = MutableStateFlow(WalletState()) + private val settingsFlow = MutableStateFlow(SettingsData()) + + private lateinit var sut: PublicPaykitRepo + + @Before + fun setUp() { + sut = createRepo() + settingsFlow.value = SettingsData() + publicKey.value = "pubkyself" + walletState.value = WalletState() + + whenever(pubkyRepo.publicKey).thenReturn(publicKey) + whenever(walletRepo.walletState).thenReturn(walletState) + whenever(settingsStore.data).thenReturn(settingsFlow) + whenever(clock.now()).thenReturn(Instant.fromEpochMilliseconds(NOW_MILLIS)) + whenever { pubkyRepo.setPaymentEndpoint(any(), any()) }.thenReturn(Result.success(Unit)) + whenever { pubkyRepo.removePaymentEndpoint(any()) }.thenReturn(Result.success(Unit)) + whenever { settingsStore.update(any()) }.thenAnswer { + val transform = it.getArgument<(SettingsData) -> SettingsData>(0) + settingsFlow.value = transform(settingsFlow.value) + Unit + } } @Test fun `syncCurrentPublishedEndpoints sets desired endpoints and removes obsolete bitkit endpoints`() = test { - val pubkyRepo = mock() - val walletRepo = mock() - val lightningRepo = mock() - val coreService = mock() - val (settingsStore, settingsFlow) = createSettingsStore() - val sut = createRepo( - pubkyRepo = pubkyRepo, - walletRepo = walletRepo, - lightningRepo = lightningRepo, - coreService = coreService, - settingsStore = settingsStore, + walletState.value = WalletState( + onchainAddress = "bc1ptest", + bolt11 = "lnbc1user", ) - - whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow("pubkyself")) - whenever(walletRepo.walletState).thenReturn( - MutableStateFlow( - WalletState( - onchainAddress = "bc1ptest", - bolt11 = "lnbc1user", - ), - ), - ) - whenever(lightningRepo.canReceive()).thenReturn(true) - stubPublicInvoice(lightningRepo, coreService, "lnbc1public", byteArrayOf(1, 2, 3)) - whenever(pubkyRepo.setPaymentEndpoint(any(), any())).thenReturn(Result.success(Unit)) - whenever(pubkyRepo.removePaymentEndpoint(any())).thenReturn(Result.success(Unit)) + stubPublicInvoice("lnbc1public", byteArrayOf(1, 2, 3)) whenever(pubkyRepo.getPaymentList("pubkyself")).thenReturn( Result.success( listOf( @@ -87,18 +101,14 @@ class PublicPaykitRepoTest : BaseUnitTest() { @Test fun `syncPublishedEndpoints removes bitkit managed endpoints and preserves lnurl`() = test { - val pubkyRepo = mock() - val (settingsStore, settingsFlow) = createSettingsStore( + setSettings( SettingsData( publicPaykitBolt11 = "lnbc1old", publicPaykitBolt11PaymentHash = "010203", publicPaykitBolt11ExpiresAtMillis = freshExpiryMillis(), ), ) - val sut = createRepo(pubkyRepo = pubkyRepo, settingsStore = settingsStore) - whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow("pubkyself")) - whenever(pubkyRepo.removePaymentEndpoint(any())).thenReturn(Result.success(Unit)) whenever(pubkyRepo.getPaymentList("pubkyself")).thenReturn( Result.success( listOf( @@ -120,48 +130,32 @@ class PublicPaykitRepoTest : BaseUnitTest() { @Test fun `syncCurrentPublishedEndpoints reuses fresh public bolt11`() = test { - val pubkyRepo = mock() - val walletRepo = mock() - val lightningRepo = mock() - val coreService = mock() - val clock = createClock() - val (settingsStore) = createSettingsStore( + setSettings( SettingsData( publicPaykitBolt11 = "lnbc1cached", publicPaykitBolt11PaymentHash = "010203", publicPaykitBolt11ExpiresAtMillis = freshExpiryMillis(), ), ) - val sut = createRepo( - pubkyRepo = pubkyRepo, - walletRepo = walletRepo, - lightningRepo = lightningRepo, - coreService = coreService, - settingsStore = settingsStore, - clock = clock, - ) - whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow("pubkyself")) - whenever(walletRepo.walletState).thenReturn(MutableStateFlow(WalletState())) whenever(lightningRepo.canReceive()).thenReturn(true) - whenever(pubkyRepo.setPaymentEndpoint(any(), any())).thenReturn(Result.success(Unit)) whenever(pubkyRepo.getPaymentList("pubkyself")).thenReturn(Result.success(emptyList())) val result = sut.syncCurrentPublishedEndpoints() assertTrue(result.isSuccess) - verify(lightningRepo, never()).createInvoice(amountSats = null, description = "", expirySeconds = 86_400u) + verify(lightningRepo, never()).createInvoice( + amountSats = null, + description = "", + expirySeconds = PUBLIC_BOLT11_EXPIRY_SECONDS, + ) verify(coreService, never()).decode(any()) verify(pubkyRepo).setPaymentEndpoint(MethodId.Bolt11.rawValue, """{"value":"lnbc1cached"}""") } @Test fun `refreshPublishedBolt11ForPayment rotates paid public bolt11`() = test { - val pubkyRepo = mock() - val walletRepo = mock() - val lightningRepo = mock() - val coreService = mock() - val (settingsStore, settingsFlow) = createSettingsStore( + setSettings( SettingsData( sharesPublicPaykitEndpoints = true, publicPaykitBolt11 = "lnbc1old", @@ -169,19 +163,8 @@ class PublicPaykitRepoTest : BaseUnitTest() { publicPaykitBolt11ExpiresAtMillis = freshExpiryMillis(), ), ) - val sut = createRepo( - pubkyRepo = pubkyRepo, - walletRepo = walletRepo, - lightningRepo = lightningRepo, - coreService = coreService, - settingsStore = settingsStore, - ) - whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow("pubkyself")) - whenever(walletRepo.walletState).thenReturn(MutableStateFlow(WalletState())) - whenever(lightningRepo.canReceive()).thenReturn(true) - stubPublicInvoice(lightningRepo, coreService, "lnbc1new", byteArrayOf(4, 5, 6)) - whenever(pubkyRepo.setPaymentEndpoint(any(), any())).thenReturn(Result.success(Unit)) + stubPublicInvoice("lnbc1new", byteArrayOf(4, 5, 6)) whenever(pubkyRepo.getPaymentList("pubkyself")).thenReturn( Result.success(listOf(paymentEntry(MethodId.Bolt11, "lnbc1old"))), ) @@ -196,10 +179,7 @@ class PublicPaykitRepoTest : BaseUnitTest() { @Test fun `refreshPublishedBolt11ForPayment ignores unrelated payment hash`() = test { - val pubkyRepo = mock() - val lightningRepo = mock() - val coreService = mock() - val (settingsStore) = createSettingsStore( + setSettings( SettingsData( sharesPublicPaykitEndpoints = true, publicPaykitBolt11 = "lnbc1old", @@ -207,29 +187,23 @@ class PublicPaykitRepoTest : BaseUnitTest() { publicPaykitBolt11ExpiresAtMillis = freshExpiryMillis(), ), ) - val sut = createRepo( - pubkyRepo = pubkyRepo, - lightningRepo = lightningRepo, - coreService = coreService, - settingsStore = settingsStore, - ) val result = sut.refreshPublishedBolt11ForPayment("unrelated") assertTrue(result.isSuccess) - verify(lightningRepo, never()).createInvoice(amountSats = null, description = "", expirySeconds = 86_400u) + verify(lightningRepo, never()).createInvoice( + amountSats = null, + description = "", + expirySeconds = PUBLIC_BOLT11_EXPIRY_SECONDS, + ) verify(pubkyRepo, never()).setPaymentEndpoint(any(), any()) } @Test fun `syncCurrentPublishedEndpoints returns SessionNotActive when no pubky session exists`() = test { - val pubkyRepo = mock() - val walletRepo = mock() - val sut = createRepo(pubkyRepo = pubkyRepo, walletRepo = walletRepo) - - whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow(null)) + publicKey.value = null whenever(pubkyRepo.currentPublicKey()).thenReturn(Result.success(null)) - whenever(walletRepo.walletState).thenReturn(MutableStateFlow(WalletState(onchainAddress = "bc1ptest"))) + walletState.value = WalletState(onchainAddress = "bc1ptest") val error = sut.syncCurrentPublishedEndpoints().exceptionOrNull() @@ -397,12 +371,12 @@ class PublicPaykitRepoTest : BaseUnitTest() { @Suppress("LongParameterList") private fun createRepo( - pubkyRepo: PubkyRepo = mock(), - walletRepo: WalletRepo = mock(), - lightningRepo: LightningRepo = mock(), - coreService: CoreService = mock(), - settingsStore: SettingsStore = createSettingsStore().first, - clock: Clock = createClock(), + pubkyRepo: PubkyRepo = this.pubkyRepo, + walletRepo: WalletRepo = this.walletRepo, + lightningRepo: LightningRepo = this.lightningRepo, + coreService: CoreService = this.coreService, + settingsStore: SettingsStore = this.settingsStore, + clock: Clock = this.clock, ) = PublicPaykitRepo( ioDispatcher = testDispatcher, pubkyRepo = pubkyRepo, @@ -418,36 +392,23 @@ class PublicPaykitRepoTest : BaseUnitTest() { endpointData = """{"value":"$value"}""", ) - private fun createSettingsStore( - initial: SettingsData = SettingsData(), - ): Pair> { - val settingsStore = mock() - val flow = MutableStateFlow(initial) - whenever(settingsStore.data).thenReturn(flow) - whenever { settingsStore.update(any()) }.thenAnswer { - val transform = it.getArgument<(SettingsData) -> SettingsData>(0) - flow.value = transform(flow.value) - Unit - } - return settingsStore to flow - } - - private fun createClock(): Clock { - val clock = mock() - whenever(clock.now()).thenReturn(Instant.fromEpochMilliseconds(NOW_MILLIS)) - return clock + private fun setSettings(settings: SettingsData) { + settingsFlow.value = settings } private fun freshExpiryMillis() = NOW_MILLIS + 1.hours.inWholeMilliseconds private suspend fun stubPublicInvoice( - lightningRepo: LightningRepo, - coreService: CoreService, bolt11: String, paymentHash: ByteArray, ) { + whenever(lightningRepo.canReceive()).thenReturn(true) whenever( - lightningRepo.createInvoice(amountSats = null, description = "", expirySeconds = 86_400u) + lightningRepo.createInvoice( + amountSats = null, + description = "", + expirySeconds = PUBLIC_BOLT11_EXPIRY_SECONDS, + ) ).thenReturn(Result.success(bolt11)) whenever(coreService.decode(bolt11)).thenReturn(Scanner.Lightning(lightningInvoice(bolt11, paymentHash))) } @@ -455,9 +416,9 @@ class PublicPaykitRepoTest : BaseUnitTest() { private fun lightningInvoice(bolt11: String, paymentHash: ByteArray) = LightningInvoice( bolt11 = bolt11, paymentHash = paymentHash, - amountSatoshis = 0u, + amountSatoshis = 0uL, timestampSeconds = 0u, - expirySeconds = 86_400u, + expirySeconds = PUBLIC_BOLT11_EXPIRY_SECONDS.toULong(), isExpired = false, description = "", networkType = NetworkType.REGTEST, diff --git a/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt b/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt index 4f0c1a58c..900101fe9 100644 --- a/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt +++ b/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt @@ -321,6 +321,18 @@ class AppViewModelSendFlowTest : BaseUnitTest() { assertNull(pendingContactPaymentContext(paymentHash)) } + @Test + fun `preserveContactPaymentContext moves active context to pending`() = test { + val paymentHash = "pending_hash" + val contactKey = "pubkycontact" + setActiveContactPaymentContext(contactKey) + + sut.preserveContactPaymentContext(paymentHash) + + assertNull(activeContactPaymentContext()) + assertEquals(contactKey, pendingContactPaymentContext(paymentHash)?.publicKey) + } + @Test fun `lightning scan uses QuickPay when enabled`() = test { val bolt11 = "lnbcrt1quickpay" @@ -474,6 +486,18 @@ class AppViewModelSendFlowTest : BaseUnitTest() { contexts[paymentHash] = ContactPaymentContext(publicKey) } + private fun setActiveContactPaymentContext(publicKey: String) { + val field = AppViewModel::class.java.getDeclaredField("activeContactPaymentContext") + field.isAccessible = true + field.set(sut, ContactPaymentContext(publicKey)) + } + + private fun activeContactPaymentContext(): ContactPaymentContext? { + val field = AppViewModel::class.java.getDeclaredField("activeContactPaymentContext") + field.isAccessible = true + return field.get(sut) as ContactPaymentContext? + } + @Suppress("UNCHECKED_CAST") private fun pendingContactPaymentContext(paymentHash: String): ContactPaymentContext? { val field = AppViewModel::class.java.getDeclaredField("pendingContactPaymentContexts")