From 71819d4d507a439e7406903859768b80151740ef Mon Sep 17 00:00:00 2001 From: Drew Carlson Date: Mon, 8 Jun 2020 10:52:55 -0700 Subject: [PATCH] Improve link parsing mechanics Ensure all networks are available before processing to ensure we don't miss any potential address targets. --- .../java/com/breadwallet/breadbox/BreadBox.kt | 9 + .../com/breadwallet/breadbox/CoreBreadBox.kt | 27 ++- .../java/com/breadwallet/tools/util/Link.kt | 26 +-- .../ui/navigation/RouterNavigator.kt | 205 ++++++++++-------- .../notification/InAppNotificationActivity.kt | 36 ++- .../ui/scanner/ScannerController.kt | 14 +- .../ui/send/SendSheetController.kt | 1 + .../breadwallet/ui/send/SendSheetHandler.kt | 25 ++- 8 files changed, 210 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/breadwallet/breadbox/BreadBox.kt b/app/src/main/java/com/breadwallet/breadbox/BreadBox.kt index de766913f8..72e5cc3ee0 100644 --- a/app/src/main/java/com/breadwallet/breadbox/BreadBox.kt +++ b/app/src/main/java/com/breadwallet/breadbox/BreadBox.kt @@ -25,6 +25,7 @@ package com.breadwallet.breadbox import com.breadwallet.crypto.Account +import com.breadwallet.crypto.Network import com.breadwallet.crypto.Wallet import com.breadwallet.crypto.System @@ -74,6 +75,14 @@ interface BreadBox { /** Emits the [WalletState] for the [Wallet] of [currencyCode]. */ fun walletState(currencyCode: String): Flow + /** + * Emits the [Network]s discovered by [System]. + * + * Setting [whenDiscoveryComplete] to true delays the first + * emission until network discovery is complete. + */ + fun networks(whenDiscoveryComplete: Boolean = false): Flow> + /** Returns [System] when [isOpen] or null when it is not. */ fun getSystemUnsafe(): System? } diff --git a/app/src/main/java/com/breadwallet/breadbox/CoreBreadBox.kt b/app/src/main/java/com/breadwallet/breadbox/CoreBreadBox.kt index 3a751fbff1..72e937d1d6 100644 --- a/app/src/main/java/com/breadwallet/breadbox/CoreBreadBox.kt +++ b/app/src/main/java/com/breadwallet/breadbox/CoreBreadBox.kt @@ -35,6 +35,7 @@ import com.breadwallet.crypto.WalletManager import com.breadwallet.crypto.WalletManagerState import com.breadwallet.crypto.blockchaindb.BlockchainDb import com.breadwallet.crypto.events.network.NetworkEvent +import com.breadwallet.crypto.events.system.SystemDiscoveredNetworksEvent import com.breadwallet.crypto.events.system.SystemEvent import com.breadwallet.crypto.events.system.SystemListener import com.breadwallet.crypto.events.system.SystemNetworkAddedEvent @@ -78,6 +79,7 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch import java.io.File import java.util.Locale @@ -121,6 +123,7 @@ internal class CoreBreadBox( private var networkManager: NetworkManager? = null + private val isDiscoveryComplete = AtomicBoolean(false) private val _isOpen = AtomicBoolean(false) override var isOpen: Boolean get() = _isOpen.get() @@ -351,6 +354,17 @@ internal class CoreBreadBox( } } + override fun networks(whenDiscoveryComplete: Boolean): Flow> = + system().transform { + if (whenDiscoveryComplete) { + if (isDiscoveryComplete.get()) { + emit(it.networks) + } + } else { + emit(it.networks) + } + } + override fun getSystemUnsafe(): System? = system override fun handleWalletEvent( @@ -448,11 +462,16 @@ internal class CoreBreadBox( override fun handleNetworkEvent(system: System, network: Network, event: NetworkEvent) = Unit override fun handleSystemEvent(system: System, event: SystemEvent) { - systemChannel.offer(Unit) - if (event is SystemNetworkAddedEvent) { - logDebug("Network '${event.network.name}' added.") - networkManager?.initializeNetwork(event.network) + when (event) { + is SystemNetworkAddedEvent -> { + logDebug("Network '${event.network.name}' added.") + networkManager?.initializeNetwork(event.network) + } + is SystemDiscoveredNetworksEvent -> { + isDiscoveryComplete.set(true) + } } + systemChannel.offer(Unit) } override fun handleTransferEvent( diff --git a/app/src/main/java/com/breadwallet/tools/util/Link.kt b/app/src/main/java/com/breadwallet/tools/util/Link.kt index 0bb3328489..642cf4b8dd 100644 --- a/app/src/main/java/com/breadwallet/tools/util/Link.kt +++ b/app/src/main/java/com/breadwallet/tools/util/Link.kt @@ -25,7 +25,6 @@ package com.breadwallet.tools.util import android.net.Uri -import com.breadwallet.app.BreadApp import com.breadwallet.breadbox.BreadBox import com.breadwallet.crypto.Address import com.breadwallet.crypto.Key @@ -38,7 +37,7 @@ import com.breadwallet.util.CryptoUriParser import com.breadwallet.util.CurrencyCode import com.platform.HTTPServer import io.sweers.redacted.annotation.Redacted -import org.kodein.di.erased.instance +import kotlinx.coroutines.flow.first import java.io.Serializable import java.math.BigDecimal import java.net.URLEncoder @@ -104,22 +103,25 @@ sealed class Link { /** Turn the url string into a [Link] or null if it is not supported. */ @Suppress("ComplexMethod") -fun String.asLink(): Link? { +suspend fun String.asLink( + breadBox: BreadBox, + uriParser: CryptoUriParser +): Link? { + if (isBlank()) return null val uri = Uri.parse(this) val (address, currencyCode) = - breadBox.getSystemUnsafe() - ?.networks - ?.mapNotNull { network -> + breadBox.networks(true) + .first() + .mapNotNull { network -> Address.create(this, network) .orNull() ?.let { address -> address to network.currency.code } } - ?.firstOrNull() ?: null to null + .firstOrNull() ?: null to null return when { - isBlank() -> null address != null && currencyCode != null -> Link.CryptoRequestUrl( currencyCode = currencyCode, @@ -143,14 +145,6 @@ fun String.asLink(): Link? { } } -private val uriParser by lazy { - BreadApp.getKodeinInstance().instance() -} - -private val breadBox by lazy { - BreadApp.getKodeinInstance().instance() -} - fun CryptoRequest.asCryptoRequestUrl() = Link.CryptoRequestUrl( address = address, diff --git a/app/src/main/java/com/breadwallet/ui/navigation/RouterNavigator.kt b/app/src/main/java/com/breadwallet/ui/navigation/RouterNavigator.kt index cd87ec8ba0..b6d38f6a53 100644 --- a/app/src/main/java/com/breadwallet/ui/navigation/RouterNavigator.kt +++ b/app/src/main/java/com/breadwallet/ui/navigation/RouterNavigator.kt @@ -32,6 +32,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.breadwallet.R +import com.breadwallet.breadbox.BreadBox import com.breadwallet.legacy.presenter.settings.NotificationSettingsController import com.breadwallet.logger.logError import com.breadwallet.protocols.messageexchange.MessageExchangeService @@ -75,17 +76,33 @@ import com.breadwallet.ui.wallet.BrdWalletController import com.breadwallet.ui.wallet.WalletController import com.breadwallet.ui.web.WebController import com.breadwallet.ui.writedownkey.WriteDownKeyController +import com.breadwallet.util.CryptoUriParser import com.breadwallet.util.isBrd import com.platform.HTTPServer import com.platform.util.AppReviewPromptManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.kodein.di.KodeinAware +import org.kodein.di.android.closestKodein +import org.kodein.di.erased.instance import java.util.Locale @Suppress("TooManyFunctions") class RouterNavigator( private val routerProvider: () -> Router -) : NavigationTargetHandlerSpec { +) : NavigationTargetHandlerSpec, KodeinAware { private val router get() = routerProvider() + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + + override val kodein by closestKodein { + checkNotNull(router.activity?.applicationContext) + } + + private val breadBox by instance() + private val uriParser by instance() fun navigateTo(target: NavigationTarget) = patch(target) @@ -203,97 +220,13 @@ class RouterNavigator( } override fun deepLink(effect: NavigationTarget.DeepLink) { - val link = effect.url?.asLink() ?: effect.link - if (link == null) { - logError("Failed to parse url, ${effect.url}") - showLaunchScreen(effect.authenticated) - return - } - val isTopLogin = router.backstack.lastOrNull()?.controller() is LoginController - if (isTopLogin && effect.authenticated) { - router.popCurrentController() - } - when (link) { - is Link.CryptoRequestUrl -> { - val sendController = SendSheetController(link).asTransaction() - router.pushWithStackIfEmpty(sendController, effect.authenticated) { - listOf( - HomeController().asTransaction(), - WalletController(link.currencyCode).asTransaction( - popChangeHandler = HorizontalChangeHandler(), - pushChangeHandler = HorizontalChangeHandler() - ), - sendController - ) - } - } - is Link.BreadUrl.ScanQR -> { - val controller = ScannerController().asTransaction() - router.pushWithStackIfEmpty(controller, effect.authenticated) { - listOf( - HomeController().asTransaction(), - controller - ) - } - } - is Link.ImportWallet -> { - val controller = ImportController( - privateKey = link.privateKey, - isPasswordProtected = link.passwordProtected - ).asTransaction() - router.pushWithStackIfEmpty(controller, effect.authenticated) { - listOf( - HomeController().asTransaction(), - controller - ) - } - } - is Link.PlatformUrl -> { - val controller = WebController(link.url).asTransaction() - router.pushWithStackIfEmpty(controller, effect.authenticated) { - listOf( - HomeController().asTransaction(), - controller - ) - } - } - is Link.PlatformDebugUrl -> { - val context = router.activity!!.applicationContext - if (!link.webBundleUrl.isNullOrBlank()) { - ServerBundlesHelper.setWebPlatformDebugURL(link.webBundleUrl) - } else if (!link.webBundle.isNullOrBlank()) { - ServerBundlesHelper.setDebugBundle( - context, - ServerBundlesHelper.Type.WEB, - link.webBundle - ) - } - - showLaunchScreen(effect.authenticated) - } - Link.BreadUrl.ScanQR -> { - val controller = ScannerController().asTransaction() - router.pushWithStackIfEmpty(controller, effect.authenticated) { - listOf( - HomeController().asTransaction(), - controller - ) - } - } - is Link.WalletPairUrl -> { - val context = router.activity!!.applicationContext - MessageExchangeService.enqueueWork( - context, MessageExchangeService.createIntent( - context, - MessageExchangeService.ACTION_REQUEST_TO_PAIR, - link.pairingMetaData - ) - ) - showLaunchScreen(effect.authenticated) - } - else -> { - logError("Failed to route deeplink, going Home.") + scope.launch(Dispatchers.Main) { + val link = effect.url?.asLink(breadBox, uriParser) ?: effect.link + if (link == null) { + logError("Failed to parse url, ${effect.url}") showLaunchScreen(effect.authenticated) + } else { + processDeepLink(effect, link) } } } @@ -582,4 +515,94 @@ class RouterNavigator( } } } + + private fun processDeepLink(effect: NavigationTarget.DeepLink, link: Link) { + val isTopLogin = router.backstack.lastOrNull()?.controller() is LoginController + if (isTopLogin && effect.authenticated) { + router.popCurrentController() + } + when (link) { + is Link.CryptoRequestUrl -> { + val sendController = SendSheetController(link).asTransaction() + router.pushWithStackIfEmpty(sendController, effect.authenticated) { + listOf( + HomeController().asTransaction(), + WalletController(link.currencyCode).asTransaction( + popChangeHandler = HorizontalChangeHandler(), + pushChangeHandler = HorizontalChangeHandler() + ), + sendController + ) + } + } + is Link.BreadUrl.ScanQR -> { + val controller = ScannerController().asTransaction() + router.pushWithStackIfEmpty(controller, effect.authenticated) { + listOf( + HomeController().asTransaction(), + controller + ) + } + } + is Link.ImportWallet -> { + val controller = ImportController( + privateKey = link.privateKey, + isPasswordProtected = link.passwordProtected + ).asTransaction() + router.pushWithStackIfEmpty(controller, effect.authenticated) { + listOf( + HomeController().asTransaction(), + controller + ) + } + } + is Link.PlatformUrl -> { + val controller = WebController(link.url).asTransaction() + router.pushWithStackIfEmpty(controller, effect.authenticated) { + listOf( + HomeController().asTransaction(), + controller + ) + } + } + is Link.PlatformDebugUrl -> { + val context = router.activity!!.applicationContext + if (!link.webBundleUrl.isNullOrBlank()) { + ServerBundlesHelper.setWebPlatformDebugURL(link.webBundleUrl) + } else if (!link.webBundle.isNullOrBlank()) { + ServerBundlesHelper.setDebugBundle( + context, + ServerBundlesHelper.Type.WEB, + link.webBundle + ) + } + + showLaunchScreen(effect.authenticated) + } + Link.BreadUrl.ScanQR -> { + val controller = ScannerController().asTransaction() + router.pushWithStackIfEmpty(controller, effect.authenticated) { + listOf( + HomeController().asTransaction(), + controller + ) + } + } + is Link.WalletPairUrl -> { + val context = router.activity!!.applicationContext + MessageExchangeService.enqueueWork( + context, MessageExchangeService.createIntent( + context, + MessageExchangeService.ACTION_REQUEST_TO_PAIR, + link.pairingMetaData + ) + ) + showLaunchScreen(effect.authenticated) + } + else -> { + logError("Failed to route deeplink, going Home.") + showLaunchScreen(effect.authenticated) + } + } + } } diff --git a/app/src/main/java/com/breadwallet/ui/notification/InAppNotificationActivity.kt b/app/src/main/java/com/breadwallet/ui/notification/InAppNotificationActivity.kt index 0d69c1d9ec..e22984dd68 100644 --- a/app/src/main/java/com/breadwallet/ui/notification/InAppNotificationActivity.kt +++ b/app/src/main/java/com/breadwallet/ui/notification/InAppNotificationActivity.kt @@ -29,18 +29,27 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import com.breadwallet.R +import com.breadwallet.breadbox.BreadBox import com.breadwallet.ext.viewModel import com.breadwallet.legacy.presenter.activities.util.BRActivity import com.breadwallet.model.InAppMessage import com.breadwallet.tools.util.asLink import com.breadwallet.ui.MainActivity +import com.breadwallet.util.CryptoUriParser import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.activity_in_app_notification.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.kodein.di.KodeinAware +import org.kodein.di.android.closestKodein +import org.kodein.di.erased.instance /** * Screen used to display an in-app notification. */ -class InAppNotificationActivity : BRActivity() { +class InAppNotificationActivity : BRActivity(), KodeinAware { companion object { private const val EXT_NOTIFICATION = "com.breadwallet.ui.notification.EXT_NOTIFICATION" @@ -53,6 +62,11 @@ class InAppNotificationActivity : BRActivity() { } } + override val kodein by closestKodein() + + private val breadBox by instance() + private val uriParser by instance() + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val viewModel by viewModel { InAppNotificationViewModel(intent.getParcelableExtra(EXT_NOTIFICATION)) } @@ -68,16 +82,20 @@ class InAppNotificationActivity : BRActivity() { viewModel.markAsRead(true) val actionUrl = viewModel.notification.actionButtonUrl if (!actionUrl.isNullOrEmpty()) { - if (actionUrl.asLink() != null) { - Intent(this, MainActivity::class.java) - .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - .putExtra(MainActivity.EXTRA_DATA, actionUrl) - .run(this::startActivity) - } else { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(actionUrl))) + scope.launch(Dispatchers.Main) { + if (actionUrl.asLink(breadBox, uriParser) != null) { + Intent(this@InAppNotificationActivity, MainActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + .putExtra(MainActivity.EXTRA_DATA, actionUrl) + .run(this@InAppNotificationActivity::startActivity) + } else { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(actionUrl))) + } + finish() } + } else { + finish() } - finish() } notification_title.text = viewModel.notification.title diff --git a/app/src/main/java/com/breadwallet/ui/scanner/ScannerController.kt b/app/src/main/java/com/breadwallet/ui/scanner/ScannerController.kt index 8da5d96ba0..0852623d57 100644 --- a/app/src/main/java/com/breadwallet/ui/scanner/ScannerController.kt +++ b/app/src/main/java/com/breadwallet/ui/scanner/ScannerController.kt @@ -31,6 +31,7 @@ import android.os.Bundle import android.view.View import androidx.core.content.ContextCompat import com.breadwallet.R +import com.breadwallet.breadbox.BreadBox import com.breadwallet.logger.logDebug import com.breadwallet.logger.logError import com.breadwallet.tools.qrcode.scannedText @@ -39,6 +40,7 @@ import com.breadwallet.tools.util.Link import com.breadwallet.tools.util.asLink import com.breadwallet.ui.BaseController import com.breadwallet.ui.MainActivity +import com.breadwallet.util.CryptoUriParser import kotlinx.android.synthetic.main.controller_scanner.* import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.Dispatchers.Main @@ -49,6 +51,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.transformLatest +import org.kodein.di.erased.instance private const val CAMERA_UI_UPDATE_MS = 100L @@ -64,13 +67,16 @@ class ScannerController( override val layoutId = R.layout.controller_scanner + private val breadBox by instance() + private val uriParser by instance() + override fun onAttach(view: View) { super.onAttach(view) val context = checkNotNull(applicationContext) val cameraPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) if (cameraPermission == PackageManager.PERMISSION_GRANTED) { - startScanner() + startScanner(breadBox, uriParser) } else { requestPermissions( arrayOf(Manifest.permission.CAMERA), @@ -83,16 +89,16 @@ class ScannerController( super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == BRConstants.CAMERA_REQUEST_ID) { when (grantResults.singleOrNull()) { - PackageManager.PERMISSION_GRANTED -> startScanner() + PackageManager.PERMISSION_GRANTED -> startScanner(breadBox, uriParser) PackageManager.PERMISSION_DENIED, null -> router.popController(this) } } } - private fun startScanner() { + private fun startScanner(breadBox: BreadBox, uriParser: CryptoUriParser) { qrdecoderview .scannedText(true) - .mapLatest { text -> text to text.asLink() } + .mapLatest { text -> text to text.asLink(breadBox, uriParser) } .flowOn(Default) .transformLatest { (text, link) -> if (link == null) { diff --git a/app/src/main/java/com/breadwallet/ui/send/SendSheetController.kt b/app/src/main/java/com/breadwallet/ui/send/SendSheetController.kt index bf22cf8b90..2e33993329 100644 --- a/app/src/main/java/com/breadwallet/ui/send/SendSheetController.kt +++ b/app/src/main/java/com/breadwallet/ui/send/SendSheetController.kt @@ -125,6 +125,7 @@ class SendSheetController(args: Bundle? = null) : get() = SendSheetHandler.create( checkNotNull(applicationContext), breadBox = direct.instance(), + uriParser = direct.instance(), userManager = direct.instance(), apiClient = direct.instance(), ratesRepository = direct.instance(), diff --git a/app/src/main/java/com/breadwallet/ui/send/SendSheetHandler.kt b/app/src/main/java/com/breadwallet/ui/send/SendSheetHandler.kt index f784dad9be..a7be35d0c6 100644 --- a/app/src/main/java/com/breadwallet/ui/send/SendSheetHandler.kt +++ b/app/src/main/java/com/breadwallet/ui/send/SendSheetHandler.kt @@ -53,6 +53,7 @@ import com.breadwallet.tools.util.Link import com.breadwallet.tools.util.asLink import com.breadwallet.ui.send.SendSheet.E import com.breadwallet.ui.send.SendSheet.F +import com.breadwallet.util.CryptoUriParser import com.breadwallet.util.CurrencyCode import com.breadwallet.util.HEADER_BITPAY_PARTNER import com.breadwallet.util.HEADER_BITPAY_PARTNER_KEY @@ -91,6 +92,7 @@ object SendSheetHandler { fun create( context: Context, breadBox: BreadBox, + uriParser: CryptoUriParser, userManager: BrdUserManager, apiClient: APIClient, ratesRepository: RatesRepository, @@ -99,15 +101,15 @@ object SendSheetHandler { ) = subtypeEffectHandler { addTransformer(pollExchangeRate(breadBox, ratesRepository)) addTransformer(handleLoadBalance(breadBox, ratesRepository)) - addTransformer(validateAddress(breadBox)) + addTransformer(validateAddress(breadBox, uriParser)) addTransformer(handleEstimateFee(breadBox)) addTransformer(handleSendTransaction(breadBox, userManager)) addTransformer(handleAddTransactionMetadata(metaDataEffectHandler)) addTransformer(handleLoadCryptoRequestData(breadBox, apiClient, context)) addTransformer(handleContinueWithPayment(userManager, breadBox)) addTransformer(handlePostPayment(apiClient)) - addFunction(handleResolvePayId(breadBox, payIdService)) - addFunction(parseClipboard(context, breadBox)) + addFunction(handleResolvePayId(breadBox, payIdService, uriParser)) + addFunction(parseClipboard(context, breadBox, uriParser)) addFunction(handleGetTransferFields(breadBox)) addFunction(handleValidateTransferFields(breadBox)) @@ -245,11 +247,13 @@ object SendSheetHandler { } private fun validateAddress( - breadBox: BreadBox + breadBox: BreadBox, + uriParser: CryptoUriParser ) = flowTransformer { effects -> effects.mapLatest { effect -> validateTargetString( breadBox, + uriParser, effect.currencyCode, effect.address ) @@ -258,14 +262,15 @@ object SendSheetHandler { private fun handleResolvePayId( breadBox: BreadBox, - payIdService: PayIdService + payIdService: PayIdService, + uriParser: CryptoUriParser ): suspend (F.ResolvePayId) -> E = { effect -> when (val result = payIdService.getAddress(effect.payId, effect.currencyCode)) { PayIdResult.NoAddress -> E.OnAddressValidated.PayId.NoAddress PayIdResult.InvalidPayId -> E.OnAddressValidated.PayId.InvalidPayId PayIdResult.ExternalError -> E.OnAddressValidated.PayId.RetrievalError is Success -> when (val validateResult = - validateTargetString(breadBox, effect.currencyCode, result.address)) { + validateTargetString(breadBox, uriParser, effect.currencyCode, result.address)) { is E.OnAddressValidated.Address.ValidAddress -> E.OnAddressValidated.PayId.ValidAddress( effect.payId, validateResult.address @@ -279,21 +284,23 @@ object SendSheetHandler { private fun parseClipboard( context: Context, - breadBox: BreadBox + breadBox: BreadBox, + uriParser: CryptoUriParser ): suspend (F.ParseClipboardData) -> E = { effect -> val text = withContext(Dispatchers.Main) { BRClipboardManager.getClipboard(context) } - validateTargetString(breadBox, effect.currencyCode, text, true) + validateTargetString(breadBox, uriParser, effect.currencyCode, text, true) } private suspend fun validateTargetString( breadBox: BreadBox, + uriParser: CryptoUriParser, currencyCode: CurrencyCode, target: String, fromClipboard: Boolean = false ): E.OnAddressValidated.Address { - val cryptoRequest = (target.asLink() as? Link.CryptoRequestUrl) + val cryptoRequest = (target.asLink(breadBox, uriParser) as? Link.CryptoRequestUrl) val reqAddress = cryptoRequest?.address ?: target return when {