diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9b45aec587..a5bcf592aa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,15 +41,6 @@ - - - - - - - - - diff --git a/app/src/main/java/com/babylon/wallet/android/MainActivity.kt b/app/src/main/java/com/babylon/wallet/android/MainActivity.kt index abe4e66273..f42fed7406 100644 --- a/app/src/main/java/com/babylon/wallet/android/MainActivity.kt +++ b/app/src/main/java/com/babylon/wallet/android/MainActivity.kt @@ -61,7 +61,10 @@ class MainActivity : FragmentActivity() { super.onCreate(savedInstanceState) cloudBackupSyncExecutor.startPeriodicChecks(lifecycleOwner = this) - intent.data?.let { viewModel.handleDeepLink(it) } + intent.data?.let { + intent.replaceExtras(Bundle()) + viewModel.handleDeepLink(it) + } setContent { RadixWalletTheme { val isVisible by balanceVisibilityObserver.isBalanceVisible.collectAsState(initial = true) @@ -100,7 +103,10 @@ class MainActivity : FragmentActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - intent.data?.let { viewModel.handleDeepLink(it) } + intent.data?.let { + this.intent.replaceExtras(Bundle()) + viewModel.handleDeepLink(it) + } } private fun setSplashExitAnimation(splashScreen: SplashScreen) { diff --git a/app/src/main/java/com/babylon/wallet/android/WalletApp.kt b/app/src/main/java/com/babylon/wallet/android/WalletApp.kt index 7e33cdc39d..5bf956c653 100644 --- a/app/src/main/java/com/babylon/wallet/android/WalletApp.kt +++ b/app/src/main/java/com/babylon/wallet/android/WalletApp.kt @@ -68,26 +68,26 @@ fun WalletApp( mainViewModel.oneOffEvent.collect { event -> when (event) { is MainEvent.IncomingRequestEvent -> { + if (event.request.needVerification) { + navController.mobileConnect(event.request.interactionId) + return@collect + } when (val incomingRequest = event.request) { is IncomingMessage.IncomingRequest.TransactionRequest -> { navController.transactionReview( - requestId = incomingRequest.interactionId.toString() + requestId = incomingRequest.interactionId ) } is IncomingMessage.IncomingRequest.AuthorizedRequest -> { - navController.dAppLoginAuthorized(incomingRequest.interactionId.toString()) + navController.dAppLoginAuthorized(incomingRequest.interactionId) } is IncomingMessage.IncomingRequest.UnauthorizedRequest -> { - navController.dAppLoginUnauthorized(incomingRequest.interactionId.toString()) + navController.dAppLoginUnauthorized(incomingRequest.interactionId) } } } - - is MainEvent.MobileConnectLink -> { - navController.mobileConnect(event.request) - } } } } @@ -177,13 +177,13 @@ fun WalletApp( dismissText = null ) } - if (!state.isProfileInitialized) { + if (state.showMobileConnectWarning) { BasicPromptAlertDialog( finish = { - onCloseApp() + mainViewModel.onMobileConnectWarningShown() }, - titleText = "No profile found", - messageText = "You need to create a profile to respond to dApp requests", + titleText = stringResource(id = R.string.mobileConnect_noProfileDialog_title), + messageText = stringResource(id = R.string.mobileConnect_noProfileDialog_subtitle), confirmText = stringResource(id = R.string.common_ok), dismissText = null ) diff --git a/app/src/main/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepository.kt b/app/src/main/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepository.kt index f325548c2f..112c9f7968 100644 --- a/app/src/main/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepository.kt +++ b/app/src/main/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepository.kt @@ -1,6 +1,8 @@ package com.babylon.wallet.android.data.dapp import com.babylon.wallet.android.domain.model.IncomingMessage.IncomingRequest +import com.babylon.wallet.android.utils.AppEvent +import com.babylon.wallet.android.utils.AppEventBus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -17,24 +19,31 @@ interface IncomingRequestRepository { suspend fun add(incomingRequest: IncomingRequest) + suspend fun addPriorityRequest(incomingRequest: IncomingRequest) + suspend fun requestHandled(requestId: String) suspend fun pauseIncomingRequests() suspend fun resumeIncomingRequests() - fun getUnauthorizedRequest(requestId: String): IncomingRequest.UnauthorizedRequest? - - fun getTransactionWriteRequest(requestId: String): IncomingRequest.TransactionRequest? - - fun getAuthorizedRequest(requestId: String): IncomingRequest.AuthorizedRequest? + fun getRequest(requestId: String): IncomingRequest? fun removeAll() fun getAmountOfRequests(): Int + + suspend fun requestDeferred(requestId: String) + + fun consumeBufferedRequest(): IncomingRequest? + + fun setBufferedRequest(request: IncomingRequest) } -class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepository { +@Suppress("TooManyFunctions") +class IncomingRequestRepositoryImpl @Inject constructor( + private val appEventBus: AppEventBus +) : IncomingRequestRepository { private val requestQueue = LinkedList() @@ -52,6 +61,19 @@ class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepos private val mutex = Mutex() + /** + * Request that can come in via deep link before wallet is setup. + */ + private var bufferedRequest: IncomingRequest? = null + + override fun setBufferedRequest(request: IncomingRequest) { + bufferedRequest = request + } + + override fun consumeBufferedRequest(): IncomingRequest? { + return bufferedRequest?.also { bufferedRequest = null } + } + override suspend fun add(incomingRequest: IncomingRequest) { mutex.withLock { val requestItem = QueueItem.RequestItem(incomingRequest) @@ -68,14 +90,57 @@ class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepos } } + /** + * There are two path of execution when using this method: + * - there is high priority screen in the queue, so the incoming request is added below it, + * taking priority over requests currently in the queue + * - there is no high priority screen: request is added at the top of queue and if there other request currently handled, + * we send a defer event for it so that UI can react and defer handling, without removing it from the queue. + * Deferred request will be handled again when top priority one handling completes + */ + override suspend fun addPriorityRequest(incomingRequest: IncomingRequest) { + mutex.withLock { + requestQueue.addFirst(QueueItem.RequestItem(incomingRequest)) + val currentRequest = _currentRequestToHandle.value + val handlingPaused = requestQueue.contains(QueueItem.HighPriorityScreen) + when { + currentRequest != null -> { + Timber.d("🗂 Deferring request with id ${currentRequest.interactionId}") + appEventBus.sendEvent(AppEvent.DeferRequestHandling(currentRequest.interactionId)) + } + + else -> { + if (!handlingPaused) { + handleNextRequest() + } + } + } + } + } + override suspend fun requestHandled(requestId: String) { mutex.withLock { - requestQueue.removeIf { it is QueueItem.RequestItem && it.incomingRequest.interactionId.toString() == requestId } + requestQueue.removeIf { it is QueueItem.RequestItem && it.incomingRequest.interactionId == requestId } + clearCurrent(requestId) handleNextRequest() Timber.d("🗂 request $requestId handled so size of list is now: ${getAmountOfRequests()}") } } + override suspend fun requestDeferred(requestId: String) { + mutex.withLock { + clearCurrent(requestId) + handleNextRequest() + Timber.d("🗂 request $requestId handled so size of list is now: ${getAmountOfRequests()}") + } + } + + private suspend fun clearCurrent(requestId: String) { + if (_currentRequestToHandle.value?.interactionId == requestId) { + _currentRequestToHandle.emit(null) + } + } + override suspend fun pauseIncomingRequests() { mutex.withLock { // If the queue knows about any high priority item, no need to add it again @@ -83,10 +148,13 @@ class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepos return } - // Put high priority item below any internal request - val topQueueItem = requestQueue.peekFirst() - if (topQueueItem is QueueItem.RequestItem && topQueueItem.incomingRequest.isInternal) { - requestQueue.add(1, QueueItem.HighPriorityScreen) + // Put high priority item below any internal request and mobile connect requests + val index = requestQueue.indexOfFirst { + val item = it as? QueueItem.RequestItem + item != null && !item.incomingRequest.isInternal && !item.incomingRequest.isMobileConnectRequest + } + if (index != -1) { + requestQueue.add(index, QueueItem.HighPriorityScreen) } else { requestQueue.addFirst(QueueItem.HighPriorityScreen) } @@ -104,37 +172,14 @@ class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepos } } - override fun getUnauthorizedRequest(requestId: String): IncomingRequest.UnauthorizedRequest? { + override fun getRequest(requestId: String): IncomingRequest? { val queueItem = requestQueue.find { - it is QueueItem.RequestItem && it.incomingRequest.interactionId.toString() == requestId && - it.incomingRequest is IncomingRequest.UnauthorizedRequest + it is QueueItem.RequestItem && it.incomingRequest.interactionId == requestId } if (queueItem == null) { - Timber.w("Unauthorized request with id $requestId is null") + Timber.w("Request with id $requestId is null") } - return (queueItem as? QueueItem.RequestItem)?.incomingRequest as? IncomingRequest.UnauthorizedRequest - } - - override fun getTransactionWriteRequest(requestId: String): IncomingRequest.TransactionRequest? { - val queueItem = requestQueue.find { - it is QueueItem.RequestItem && it.incomingRequest.interactionId.toString() == requestId && - it.incomingRequest is IncomingRequest.TransactionRequest - } - if (queueItem == null) { - Timber.w("Transaction request with id $requestId is null") - } - return (queueItem as? QueueItem.RequestItem)?.incomingRequest as? IncomingRequest.TransactionRequest - } - - override fun getAuthorizedRequest(requestId: String): IncomingRequest.AuthorizedRequest? { - val queueItem = requestQueue.find { - it is QueueItem.RequestItem && it.incomingRequest.interactionId.toString() == requestId && - it.incomingRequest is IncomingRequest.AuthorizedRequest - } - if (queueItem == null) { - Timber.w("Authorized request with id $requestId is null") - } - return (queueItem as? QueueItem.RequestItem)?.incomingRequest as? IncomingRequest.AuthorizedRequest + return (queueItem as? QueueItem.RequestItem)?.incomingRequest } override fun removeAll() { @@ -149,14 +194,13 @@ class IncomingRequestRepositoryImpl @Inject constructor() : IncomingRequestRepos // In order to emit an incoming request, the topmost item should be // a. An incoming request // b. It should not be the same as the one being handled already - if (nextRequest is QueueItem.RequestItem && _currentRequestToHandle.value != nextRequest) { + if (nextRequest is QueueItem.RequestItem && _currentRequestToHandle.value != nextRequest.incomingRequest) { _currentRequestToHandle.emit(nextRequest.incomingRequest) } } private sealed interface QueueItem { data object HighPriorityScreen : QueueItem - data class RequestItem(val incomingRequest: IncomingRequest) : QueueItem } } diff --git a/app/src/main/java/com/babylon/wallet/android/domain/model/IncomingMessage.kt b/app/src/main/java/com/babylon/wallet/android/domain/model/IncomingMessage.kt index 7c42c4ccfa..c7ec002a3a 100644 --- a/app/src/main/java/com/babylon/wallet/android/domain/model/IncomingMessage.kt +++ b/app/src/main/java/com/babylon/wallet/android/domain/model/IncomingMessage.kt @@ -25,9 +25,11 @@ sealed interface IncomingMessage { val value: String - data class RadixMobileConnectRemoteSession(val id: String) : RemoteEntityID { + data class RadixMobileConnectRemoteSession(val id: String, val originVerificationUrl: String? = null) : RemoteEntityID { override val value: String get() = id + + val needOriginVerification: Boolean = originVerificationUrl != null } data class ConnectorId(val id: String) : RemoteEntityID { @@ -50,6 +52,9 @@ sealed interface IncomingMessage { val isMobileConnectRequest: Boolean get() = remoteEntityId is RemoteEntityID.RadixMobileConnectRemoteSession + val needVerification: Boolean + get() = (remoteEntityId as? RemoteEntityID.RadixMobileConnectRemoteSession)?.needOriginVerification == true + val blockUntilComplete: Boolean get() { return metadata.blockUntilCompleted diff --git a/app/src/main/java/com/babylon/wallet/android/domain/model/deeplink/DeepLinkEvent.kt b/app/src/main/java/com/babylon/wallet/android/domain/model/deeplink/DeepLinkEvent.kt deleted file mode 100644 index 74a45a9ad1..0000000000 --- a/app/src/main/java/com/babylon/wallet/android/domain/model/deeplink/DeepLinkEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.babylon.wallet.android.domain.model.deeplink - -import com.radixdlt.sargon.RadixConnectMobileSessionRequest - -sealed interface DeepLinkEvent { - - data class MobileConnectVerifyRequest( - val request: RadixConnectMobileSessionRequest - ) : DeepLinkEvent -} diff --git a/app/src/main/java/com/babylon/wallet/android/domain/usecases/VerifyDAppUseCase.kt b/app/src/main/java/com/babylon/wallet/android/domain/usecases/VerifyDAppUseCase.kt index bdb2b9b108..71d33202a2 100644 --- a/app/src/main/java/com/babylon/wallet/android/domain/usecases/VerifyDAppUseCase.kt +++ b/app/src/main/java/com/babylon/wallet/android/domain/usecases/VerifyDAppUseCase.kt @@ -12,6 +12,7 @@ import com.radixdlt.sargon.AccountAddress import com.radixdlt.sargon.DappWalletInteractionErrorType import com.radixdlt.sargon.extensions.init import rdx.works.core.domain.DApp +import rdx.works.core.sargon.currentGateway import rdx.works.core.then import rdx.works.profile.domain.GetProfileUseCase import javax.inject.Inject @@ -24,6 +25,19 @@ class VerifyDAppUseCase @Inject constructor( ) { suspend operator fun invoke(request: IncomingRequest): Result { + val networkId = getProfileUseCase().currentGateway.network.id + if (networkId != request.metadata.networkId) { + respondToIncomingRequestUseCase.respondWithFailure( + request = request, + error = DappWalletInteractionErrorType.INVALID_REQUEST + ) + return Result.failure( + RadixWalletException.DappRequestException.WrongNetwork( + currentNetworkId = networkId, + requestNetworkId = request.metadata.networkId + ) + ) + } val dAppDefinitionAddress = runCatching { AccountAddress.init(request.metadata.dAppDefinitionAddress) }.getOrElse { respondToIncomingRequestUseCase.respondWithFailure( request = request, @@ -31,7 +45,6 @@ class VerifyDAppUseCase @Inject constructor( ) return Result.failure(RadixWalletException.DappRequestException.InvalidRequest) } - val developerMode = getProfileUseCase().appPreferences.security.isDeveloperModeEnabled return if (developerMode) { Result.success(true) diff --git a/app/src/main/java/com/babylon/wallet/android/domain/usecases/deeplink/ProcessDeepLinkUseCase.kt b/app/src/main/java/com/babylon/wallet/android/domain/usecases/deeplink/ProcessDeepLinkUseCase.kt index 069246d26c..af601bb1fd 100644 --- a/app/src/main/java/com/babylon/wallet/android/domain/usecases/deeplink/ProcessDeepLinkUseCase.kt +++ b/app/src/main/java/com/babylon/wallet/android/domain/usecases/deeplink/ProcessDeepLinkUseCase.kt @@ -3,33 +3,39 @@ package com.babylon.wallet.android.domain.usecases.deeplink import com.babylon.wallet.android.data.dapp.IncomingRequestRepository import com.babylon.wallet.android.data.dapp.model.toDomainModel import com.babylon.wallet.android.domain.model.IncomingMessage -import com.babylon.wallet.android.domain.model.deeplink.DeepLinkEvent import com.radixdlt.sargon.RadixConnectMobile +import rdx.works.profile.domain.GetProfileUseCase import timber.log.Timber import javax.inject.Inject class ProcessDeepLinkUseCase @Inject constructor( private val radixConnectMobile: RadixConnectMobile, + private val getProfileUseCase: GetProfileUseCase, private val incomingRequestRepository: IncomingRequestRepository ) { - suspend operator fun invoke(deepLink: String): DeepLinkEvent? { - val sessionRequest = runCatching { radixConnectMobile.handleDeepLink(deepLink) }.onFailure { + suspend operator fun invoke(deepLink: String): Result { + return runCatching { + val profileInitialized = getProfileUseCase.isInitialized() + val sessionRequest = radixConnectMobile.handleDeepLink(deepLink) + val request = sessionRequest.interaction.toDomainModel( + remoteEntityId = IncomingMessage.RemoteEntityID.RadixMobileConnectRemoteSession( + id = sessionRequest.sessionId.toString(), + originVerificationUrl = if (sessionRequest.originRequiresValidation) sessionRequest.origin else null + ) + ).getOrThrow() + if (!profileInitialized) { + incomingRequestRepository.setBufferedRequest(request) + return Result.success(DeepLinkProcessingResult.Buffered) + } + DeepLinkProcessingResult.Processed(request) + }.onFailure { Timber.d("Failed to parse deep link: $deepLink. Error: ${it.message}") - return null - }.getOrThrow() - - return if (sessionRequest.originRequiresValidation) { - DeepLinkEvent.MobileConnectVerifyRequest( - request = sessionRequest - ) - } else { - incomingRequestRepository.add( - sessionRequest.interaction.toDomainModel( - remoteEntityId = IncomingMessage.RemoteEntityID.RadixMobileConnectRemoteSession(sessionRequest.sessionId.toString()) - ).getOrThrow() - ) - null } } } + +sealed interface DeepLinkProcessingResult { + data object Buffered : DeepLinkProcessingResult + data class Processed(val request: IncomingMessage.IncomingRequest) : DeepLinkProcessingResult +} diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/deriveaccounts/DeriveAccountsNavGraph.kt b/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/deriveaccounts/DeriveAccountsNavGraph.kt index 3870c077cb..84a7e819fb 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/deriveaccounts/DeriveAccountsNavGraph.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/deriveaccounts/DeriveAccountsNavGraph.kt @@ -5,6 +5,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog +import com.babylon.wallet.android.presentation.navigation.markAsHighPriority fun NavController.deriveAccounts() { navigate("derive_accounts_bottom_sheet") @@ -13,6 +14,7 @@ fun NavController.deriveAccounts() { fun NavGraphBuilder.deriveAccounts( onDismiss: () -> Unit ) { + markAsHighPriority("derive_accounts_bottom_sheet") dialog( route = "derive_accounts_bottom_sheet", dialogProperties = DialogProperties(usePlatformDefaultWidth = false) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/derivepublickey/DerivePublicKeyNavGraph.kt b/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/derivepublickey/DerivePublicKeyNavGraph.kt index b6c87a90b6..f3ba812476 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/derivepublickey/DerivePublicKeyNavGraph.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/accessfactorsources/derivepublickey/DerivePublicKeyNavGraph.kt @@ -5,6 +5,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog +import com.babylon.wallet.android.presentation.navigation.markAsHighPriority fun NavController.derivePublicKey() { navigate("derive_public_key_bottom_sheet") @@ -13,6 +14,7 @@ fun NavController.derivePublicKey() { fun NavGraphBuilder.derivePublicKey( onDismiss: () -> Unit ) { + markAsHighPriority("derive_public_key_bottom_sheet") dialog( route = "derive_public_key_bottom_sheet", dialogProperties = DialogProperties(usePlatformDefaultWidth = false) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginNav.kt b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginNav.kt index 2645df5426..f980f49380 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginNav.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginNav.kt @@ -6,6 +6,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument @@ -23,8 +24,8 @@ internal class DAppAuthorizedLoginArgs(val interactionId: String) { constructor(savedStateHandle: SavedStateHandle) : this(checkNotNull(savedStateHandle[ARG_INTERACTION_ID]) as String) } -fun NavController.dAppLoginAuthorized(requestId: String) { - navigate("dapp_login_authorized/$requestId") +fun NavController.dAppLoginAuthorized(requestId: String, navOptionsBuilder: NavOptionsBuilder.() -> Unit = {}) { + navigate("dapp_login_authorized/$requestId", navOptionsBuilder) } @Suppress("LongParameterList") diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginViewModel.kt index 0ab6b9ffe4..88c935d864 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/authorized/login/DAppAuthorizedLoginViewModel.kt @@ -45,6 +45,7 @@ import com.radixdlt.sargon.SharedToDappWithPersonaAccountAddresses import com.radixdlt.sargon.extensions.ReferencesToAuthorizedPersonas import com.radixdlt.sargon.extensions.init import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -62,7 +63,6 @@ import rdx.works.core.sargon.updateDAppAuthorizedPersonaSharedAccounts import rdx.works.profile.data.repository.DAppConnectionRepository import rdx.works.profile.domain.GetProfileUseCase import rdx.works.profile.domain.ProfileException -import rdx.works.profile.domain.gateway.GetCurrentGatewayUseCase import javax.inject.Inject @HiltViewModel @@ -73,7 +73,6 @@ class DAppAuthorizedLoginViewModel @Inject constructor( private val respondToIncomingRequestUseCase: RespondToIncomingRequestUseCase, private val dAppConnectionRepository: DAppConnectionRepository, private val getProfileUseCase: GetProfileUseCase, - private val getCurrentGatewayUseCase: GetCurrentGatewayUseCase, private val stateRepository: StateRepository, private val incomingRequestRepository: IncomingRequestRepository, private val buildAuthorizedDappResponseUseCase: BuildAuthorizedDappResponseUseCase @@ -92,25 +91,23 @@ class DAppAuthorizedLoginViewModel @Inject constructor( override fun initialState(): DAppLoginUiState = DAppLoginUiState() init { + viewModelScope.launch { + appEventBus.events.filterIsInstance().collect { + if (it.interactionId == args.interactionId) { + sendEvent(Event.CloseLoginFlow) + incomingRequestRepository.requestDeferred(args.interactionId) + } + } + } observeSigningState() viewModelScope.launch { - val requestToHandle = incomingRequestRepository.getAuthorizedRequest(args.interactionId) + val requestToHandle = incomingRequestRepository.getRequest(args.interactionId) as? AuthorizedRequest if (requestToHandle == null) { sendEvent(Event.CloseLoginFlow) return@launch } else { request = requestToHandle } - val currentNetworkId = getCurrentGatewayUseCase().network.id - if (currentNetworkId != request.requestMetadata.networkId) { - handleRequestError( - RadixWalletException.DappRequestException.WrongNetwork( - currentNetworkId, - request.requestMetadata.networkId - ) - ) - return@launch - } val dAppDefinitionAddress = runCatching { AccountAddress.init(request.requestMetadata.dAppDefinitionAddress) }.getOrNull() if (!request.isValidRequest() || dAppDefinitionAddress == null) { handleRequestError(RadixWalletException.DappRequestException.InvalidRequest) @@ -587,7 +584,7 @@ class DAppAuthorizedLoginViewModel @Inject constructor( fun onAbortDappLogin(walletWalletErrorType: DappWalletInteractionErrorType = DappWalletInteractionErrorType.REJECTED_BY_USER) { viewModelScope.launch { - incomingRequestRepository.requestHandled(request.interactionId.toString()) + incomingRequestRepository.requestHandled(request.interactionId) if (!request.isInternal) { respondToIncomingRequestUseCase.respondWithFailure(request, walletWalletErrorType) } @@ -692,7 +689,7 @@ class DAppAuthorizedLoginViewModel @Inject constructor( mutex.withLock { editedDapp?.let { dAppConnectionRepository.updateOrCreateAuthorizedDApp(it) } } - incomingRequestRepository.requestHandled(request.interactionId.toString()) + incomingRequestRepository.requestHandled(request.interactionId) sendEvent(Event.LoginFlowCompleted) } else { buildAuthorizedDappResponseUseCase( diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginNav.kt b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginNav.kt index c53acf44e7..67ba307d65 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginNav.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginNav.kt @@ -6,6 +6,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument @@ -17,12 +18,12 @@ internal const val ARG_REQUEST_ID = "request_id" const val ROUTE_DAPP_LOGIN_UNAUTHORIZED_GRAPH = "dapp_login_unauthorized/{$ARG_REQUEST_ID}" const val ROUTE_DAPP_LOGIN_UNAUTHORIZED_SCREEN = "dapp_login_unauthorized_screen/{$ARG_REQUEST_ID}" -internal class DAppUnauthorizedLoginArgs(val requestId: String) { +internal class DAppUnauthorizedLoginArgs(val interactionId: String) { constructor(savedStateHandle: SavedStateHandle) : this(checkNotNull(savedStateHandle[ARG_REQUEST_ID]) as String) } -fun NavController.dAppLoginUnauthorized(requestId: String) { - navigate("dapp_login_unauthorized/$requestId") +fun NavController.dAppLoginUnauthorized(requestId: String, navOptionsBuilder: NavOptionsBuilder.() -> Unit = {}) { + navigate("dapp_login_unauthorized/$requestId", navOptionsBuilder) } @Suppress("LongParameterList") diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginViewModel.kt index cf6d3edfe3..7dbadf6552 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/dapp/unauthorized/login/DAppUnauthorizedLoginViewModel.kt @@ -35,6 +35,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import rdx.works.core.domain.DApp @@ -45,7 +46,6 @@ import rdx.works.core.sargon.fields import rdx.works.core.sargon.toPersonaData import rdx.works.profile.domain.GetProfileUseCase import rdx.works.profile.domain.ProfileException -import rdx.works.profile.domain.gateway.GetCurrentGatewayUseCase import javax.inject.Inject @HiltViewModel @@ -55,7 +55,6 @@ class DAppUnauthorizedLoginViewModel @Inject constructor( private val respondToIncomingRequestUseCase: RespondToIncomingRequestUseCase, private val appEventBus: AppEventBus, private val getProfileUseCase: GetProfileUseCase, - private val getCurrentGatewayUseCase: GetCurrentGatewayUseCase, private val stateRepository: StateRepository, private val incomingRequestRepository: IncomingRequestRepository, private val buildUnauthorizedDappResponseUseCase: BuildUnauthorizedDappResponseUseCase @@ -69,23 +68,23 @@ class DAppUnauthorizedLoginViewModel @Inject constructor( init { observeSigningState() viewModelScope.launch { - val requestToHandle = incomingRequestRepository.getUnauthorizedRequest(args.requestId) + appEventBus.events.filterIsInstance().collect { + if (it.interactionId == args.interactionId) { + sendEvent(Event.CloseLoginFlow) + incomingRequestRepository.requestDeferred(args.interactionId) + } + } + } + viewModelScope.launch { + val requestToHandle = incomingRequestRepository.getRequest( + args.interactionId + ) as? IncomingMessage.IncomingRequest.UnauthorizedRequest if (requestToHandle == null) { sendEvent(Event.CloseLoginFlow) return@launch } else { request = requestToHandle } - val currentNetworkId = getCurrentGatewayUseCase().network.id - if (currentNetworkId != request.requestMetadata.networkId) { - handleRequestError( - RadixWalletException.DappRequestException.WrongNetwork( - currentNetworkId, - request.requestMetadata.networkId - ) - ) - return@launch - } val dAppDefinitionAddress = runCatching { AccountAddress.init(request.metadata.dAppDefinitionAddress) }.getOrNull() if (!request.isValidRequest() || dAppDefinitionAddress == null) { handleRequestError(RadixWalletException.DappRequestException.InvalidRequest) @@ -187,7 +186,7 @@ class DAppUnauthorizedLoginViewModel @Inject constructor( respondToIncomingRequestUseCase.respondWithFailure(request, exception.ceError, exception.getDappMessage()) _state.update { it.copy(failureDialogState = FailureDialogState.Closed) } sendEvent(Event.CloseLoginFlow) - incomingRequestRepository.requestHandled(requestId = args.requestId) + incomingRequestRepository.requestHandled(requestId = args.interactionId) } fun onMessageShown() { @@ -218,7 +217,7 @@ class DAppUnauthorizedLoginViewModel @Inject constructor( fun onRejectRequest() { viewModelScope.launch { - incomingRequestRepository.requestHandled(requestId = args.requestId) + incomingRequestRepository.requestHandled(requestId = args.interactionId) respondToIncomingRequestUseCase.respondWithFailure(request, DappWalletInteractionErrorType.REJECTED_BY_USER) sendEvent(Event.CloseLoginFlow) } @@ -260,7 +259,7 @@ class DAppUnauthorizedLoginViewModel @Inject constructor( if (!request.isInternal) { appEventBus.sendEvent( AppEvent.Status.DappInteraction( - requestId = request.interactionId.toString(), + requestId = request.interactionId, dAppName = state.value.dapp?.name, isMobileConnect = result is IncomingRequestResponse.SuccessRadixMobileConnect ) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/main/MainViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/main/MainViewModel.kt index 31d5a714a6..1ef3083078 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/main/MainViewModel.kt @@ -7,9 +7,9 @@ import com.babylon.wallet.android.data.dapp.PeerdroidClient import com.babylon.wallet.android.data.repository.p2plink.P2PLinksRepository import com.babylon.wallet.android.domain.RadixWalletException import com.babylon.wallet.android.domain.model.IncomingMessage.IncomingRequest -import com.babylon.wallet.android.domain.model.deeplink.DeepLinkEvent import com.babylon.wallet.android.domain.usecases.AuthorizeSpecifiedPersonaUseCase import com.babylon.wallet.android.domain.usecases.VerifyDAppUseCase +import com.babylon.wallet.android.domain.usecases.deeplink.DeepLinkProcessingResult import com.babylon.wallet.android.domain.usecases.deeplink.ProcessDeepLinkUseCase import com.babylon.wallet.android.domain.usecases.p2plink.ObserveAccountsAndSyncWithConnectorExtensionUseCase import com.babylon.wallet.android.presentation.common.OneOffEvent @@ -23,7 +23,6 @@ import com.babylon.wallet.android.utils.DeviceCapabilityHelper import com.radixdlt.sargon.Account import com.radixdlt.sargon.NetworkId import com.radixdlt.sargon.Persona -import com.radixdlt.sargon.RadixConnectMobileSessionRequest import com.radixdlt.sargon.RadixConnectPassword import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -150,10 +149,20 @@ class MainViewModel @Inject constructor( }.collect() } handleAllIncomingRequests() - viewModelScope.launch { observeAccountsAndSyncWithConnectorExtensionUseCase() } + processBufferedDeepLinkRequest() + } + + private fun processBufferedDeepLinkRequest() { + viewModelScope.launch { + appEventBus.events.filterIsInstance().collect { + incomingRequestRepository.consumeBufferedRequest()?.let { request -> + verifyIncomingRequest(request) + } + } + } } override fun initialState(): MainUiState { @@ -214,14 +223,14 @@ class MainViewModel @Inject constructor( private fun handleAllIncomingRequests() { viewModelScope.launch { incomingRequestRepository.currentRequestToHandle.collect { request -> - if (request.metadata.isInternal) { + if (request.metadata.isInternal || request.isMobileConnectRequest) { sendEvent(MainEvent.IncomingRequestEvent(request)) } else { delay(REQUEST_HANDLING_DELAY) authorizeSpecifiedPersonaUseCase.invoke(request).onSuccess { dAppData -> appEventBus.sendEvent( AppEvent.Status.DappInteraction( - requestId = dAppData.interactionId.toString(), + requestId = dAppData.interactionId, dAppName = dAppData.name ) ) @@ -230,7 +239,7 @@ class MainViewModel @Inject constructor( when (dappRequestFailure) { RadixWalletException.DappRequestException.InvalidPersona, RadixWalletException.DappRequestException.InvalidRequest -> { - incomingRequestRepository.requestHandled(request.interactionId.toString()) + incomingRequestRepository.requestHandled(request.interactionId) _state.update { state -> state.copy(dappRequestFailure = dappRequestFailure) } @@ -248,38 +257,50 @@ class MainViewModel @Inject constructor( } fun handleDeepLink(deepLink: Uri) { - Timber.d("dApp deep link: $deepLink") - viewModelScope.launch { - val profileInitialized = getProfileUseCase.isInitialized() - if (!profileInitialized) { - _state.update { - it.copy(isProfileInitialized = false) - } - return@launch - } + processDeepLinkUseCase(deepLink.toString()).onSuccess { result -> + when (result) { + is DeepLinkProcessingResult.Processed -> { + verifyIncomingRequest(result.request) + } - runCatching { - processDeepLinkUseCase(deepLink.toString()) - }.onSuccess { event -> - if (event is DeepLinkEvent.MobileConnectVerifyRequest) { - sendEvent(MainEvent.MobileConnectLink(event.request)) + DeepLinkProcessingResult.Buffered -> { + _state.update { + it.copy(showMobileConnectWarning = true) + } + } } - }.onFailure { - Timber.d(it) + }.onFailure { error -> + Timber.d(error) } } } + fun onMobileConnectWarningShown() { + _state.update { + it.copy(showMobileConnectWarning = false) + } + } + private fun verifyIncomingRequest(request: IncomingRequest) { verifyingDappRequestJob = viewModelScope.launch { verifyDappUseCase(request).onSuccess { verified -> if (verified) { - incomingRequestRepository.add(request) + if (request.isMobileConnectRequest) { + incomingRequestRepository.addPriorityRequest(request) + } else { + incomingRequestRepository.add(request) + } } - }.onFailure { - _state.update { state -> - state.copy(dappRequestFailure = RadixWalletException.DappRequestException.InvalidRequest) + }.onFailure { error -> + if (error is RadixWalletException.DappRequestException) { + _state.update { + it.copy(dappRequestFailure = error) + } + } else { + _state.update { + it.copy(dappRequestFailure = RadixWalletException.DappRequestException.InvalidRequest) + } } } } @@ -354,7 +375,6 @@ class MainViewModel @Inject constructor( sealed class MainEvent : OneOffEvent { data class IncomingRequestEvent(val request: IncomingRequest) : MainEvent() - data class MobileConnectLink(val request: RadixConnectMobileSessionRequest) : MainEvent() } data class MainUiState( @@ -363,7 +383,7 @@ data class MainUiState( val dappRequestFailure: RadixWalletException.DappRequestException? = null, val olympiaErrorState: OlympiaErrorState? = null, val claimedByAnotherDeviceError: ClaimedByAnotherDevice? = null, - val isProfileInitialized: Boolean = true + val showMobileConnectWarning: Boolean = false ) : UiState data class OlympiaErrorState( diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkNav.kt b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkNav.kt index 68f47a3e93..728215688b 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkNav.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkNav.kt @@ -7,35 +7,36 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.radixdlt.sargon.RadixConnectMobileSessionRequest -import com.radixdlt.sargon.extensions.fromJson -import com.radixdlt.sargon.extensions.toJson +import com.babylon.wallet.android.presentation.navigation.markAsHighPriority -private const val ARG_REQUEST = "request" -private const val ROUTE_ARGS = "$ARG_REQUEST={$ARG_REQUEST}" -private const val ROUTE = "mobileConnect?$ROUTE_ARGS" +private const val ARG_INTERACTION_ID = "interactionId" +const val ROUTE_MOBILE_CONNECT = "mobileConnect/{$ARG_INTERACTION_ID}" fun NavController.mobileConnect( - request: RadixConnectMobileSessionRequest + interactionId: String ) { - navigate(route = "mobileConnect?$ARG_REQUEST=${request.toJson()}") + navigate(route = "mobileConnect/$interactionId") } internal class MobileConnectArgs( - val request: RadixConnectMobileSessionRequest + val interactionId: String ) { constructor(savedStateHandle: SavedStateHandle) : this( - request = checkNotNull(savedStateHandle.get(ARG_REQUEST)).let { - RadixConnectMobileSessionRequest.fromJson(it) - } + interactionId = checkNotNull(savedStateHandle.get(ARG_INTERACTION_ID)) ) } -fun NavGraphBuilder.mobileConnect(onBackClick: () -> Unit) { +fun NavGraphBuilder.mobileConnect( + onBackClick: () -> Unit, + onHandleRequestAuthorizedRequest: (String) -> Unit, + onHandleUnauthorizedRequest: (String) -> Unit, + onHandleTransactionRequest: (String) -> Unit +) { + markAsHighPriority(route = ROUTE_MOBILE_CONNECT) composable( - route = ROUTE, + route = ROUTE_MOBILE_CONNECT, arguments = listOf( - navArgument(ARG_REQUEST) { + navArgument(ARG_INTERACTION_ID) { type = NavType.StringType } ), @@ -46,6 +47,11 @@ fun NavGraphBuilder.mobileConnect(onBackClick: () -> Unit) { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down) } ) { - MobileConnectLinkScreen(onClose = onBackClick) + MobileConnectLinkScreen( + onClose = onBackClick, + onHandleRequestAuthorizedRequest = onHandleRequestAuthorizedRequest, + onHandleUnauthorizedRequest = onHandleUnauthorizedRequest, + onHandleTransactionRequest = onHandleTransactionRequest + ) } } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkScreen.kt b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkScreen.kt index 5ade22f073..8b73648f50 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkScreen.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkScreen.kt @@ -1,7 +1,12 @@ package com.babylon.wallet.android.presentation.mobileconnect import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -11,6 +16,9 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -25,10 +33,12 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.babylon.wallet.android.R import com.babylon.wallet.android.designsystem.theme.RadixTheme +import com.babylon.wallet.android.domain.model.IncomingMessage import com.babylon.wallet.android.presentation.ui.RadixWalletPreviewTheme import com.babylon.wallet.android.presentation.ui.composables.BackIconType import com.babylon.wallet.android.presentation.ui.composables.BottomPrimaryButton @@ -43,13 +53,31 @@ import com.radixdlt.sargon.annotation.UsesSampleValues fun MobileConnectLinkScreen( modifier: Modifier = Modifier, viewModel: MobileConnectLinkViewModel = hiltViewModel(), - onClose: () -> Unit + onClose: () -> Unit, + onHandleRequestAuthorizedRequest: (String) -> Unit, + onHandleUnauthorizedRequest: (String) -> Unit, + onHandleTransactionRequest: (String) -> Unit ) { val state by viewModel.state.collectAsStateWithLifecycle() LaunchedEffect(Unit) { viewModel.oneOffEvent.collect { event -> when (event) { MobileConnectLinkViewModel.Event.Close -> onClose() + is MobileConnectLinkViewModel.Event.HandleRequest -> { + when (event.request) { + is IncomingMessage.IncomingRequest.AuthorizedRequest -> { + onHandleRequestAuthorizedRequest(event.request.interactionId) + } + + is IncomingMessage.IncomingRequest.TransactionRequest -> { + onHandleTransactionRequest(event.request.interactionId) + } + + is IncomingMessage.IncomingRequest.UnauthorizedRequest -> { + onHandleUnauthorizedRequest(event.request.interactionId) + } + } + } } } } @@ -111,7 +139,8 @@ fun MobileConnectLinkContent( Column( modifier = Modifier .fillMaxSize() - .padding(padding), + .padding(padding) + .verticalScroll(state = rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(RadixTheme.dimensions.paddingXXXXLarge)) @@ -126,20 +155,20 @@ fun MobileConnectLinkContent( modifier = Modifier .fillMaxWidth() .padding(horizontal = RadixTheme.dimensions.paddingXXXLarge), - text = stringResource(id = R.string.mobileConnect_verifyingDApp_title, dAppDisplayName), + text = stringResource(id = R.string.mobileConnect_linkTitle), color = RadixTheme.colors.gray1, - style = RadixTheme.typography.title, // TODO Mobile Connect (UI) + style = RadixTheme.typography.title, textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.height(RadixTheme.dimensions.paddingXXXXLarge)) + Spacer(modifier = Modifier.height(RadixTheme.dimensions.paddingXXXLarge)) Text( modifier = Modifier .fillMaxWidth() .padding(horizontal = RadixTheme.dimensions.paddingXXXLarge), text = buildAnnotatedString { val valueToDisplay = stringResource( - id = R.string.mobileConnect_verifyingDApp_subtitle, + id = R.string.mobileConnect_linkSubtitle, dAppDisplayName ) @@ -147,28 +176,59 @@ fun MobileConnectLinkContent( val startOfSpan = valueToDisplay.indexOf(dAppDisplayName) addStyle( - style = RadixTheme.typography.body1StandaloneLink.toSpanStyle(), // TODO Mobile Connect (UI) + style = RadixTheme.typography.body2Header.copy(fontSize = 16.sp).toSpanStyle(), start = startOfSpan, end = startOfSpan + dAppDisplayName.length, ) }, - color = RadixTheme.colors.gray2, - style = RadixTheme.typography.body1HighImportance, // TODO Mobile Connect (UI) + color = RadixTheme.colors.gray1, + style = RadixTheme.typography.body1Link, textAlign = TextAlign.Center ) - - Spacer(modifier = Modifier.weight(1f)) - Text( + Spacer(modifier = Modifier.height(RadixTheme.dimensions.paddingXXXLarge)) + Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = RadixTheme.dimensions.paddingXXXLarge), - text = stringResource(id = R.string.mobileConnect_verifyingDApp_body), + .padding(horizontal = RadixTheme.dimensions.paddingXXXLarge) + .background(color = RadixTheme.colors.gray5, shape = RadixTheme.shapes.roundedRectSmall) + .padding(vertical = RadixTheme.dimensions.paddingLarge, horizontal = RadixTheme.dimensions.paddingDefault), + verticalArrangement = Arrangement.spacedBy(RadixTheme.dimensions.paddingDefault) + ) { + NumberedListItem(number = 1, text = stringResource(id = R.string.mobileConnect_linkBody1)) + HorizontalDivider(color = RadixTheme.colors.gray4) + NumberedListItem(number = 2, text = stringResource(id = R.string.mobileConnect_linkBody2)) + } + Spacer(modifier = Modifier.weight(1f)) + } + } +} + +@Composable +private fun NumberedListItem(modifier: Modifier = Modifier, number: Int, text: String) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(RadixTheme.dimensions.paddingDefault) + ) { + Box( + modifier = Modifier + .size(28.dp) + .border(1.dp, RadixTheme.colors.gray1, RadixTheme.shapes.circle) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = number.toString(), color = RadixTheme.colors.gray1, - style = RadixTheme.typography.body1Regular, // TODO Mobile Connect (UI) - textAlign = TextAlign.Center + style = RadixTheme.typography.body1Header.copy(fontSize = 20.sp), + textAlign = TextAlign.Start ) - Spacer(modifier = Modifier.weight(1f)) } + Text( + text = text, + color = RadixTheme.colors.gray1, + style = RadixTheme.typography.body1Regular, + textAlign = TextAlign.Start + ) } } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkViewModel.kt index e45d36482d..94a6ffb6bd 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/mobileconnect/MobileConnectLinkViewModel.kt @@ -3,24 +3,26 @@ package com.babylon.wallet.android.presentation.mobileconnect import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.babylon.wallet.android.data.dapp.IncomingRequestRepository -import com.babylon.wallet.android.data.dapp.model.toDomainModel import com.babylon.wallet.android.data.repository.dapps.WellKnownDAppDefinitionRepository +import com.babylon.wallet.android.di.coroutines.ApplicationScope +import com.babylon.wallet.android.domain.model.IncomingMessage import com.babylon.wallet.android.domain.model.IncomingMessage.RemoteEntityID.RadixMobileConnectRemoteSession import com.babylon.wallet.android.domain.usecases.GetDAppsUseCase +import com.babylon.wallet.android.domain.usecases.RespondToIncomingRequestUseCase import com.babylon.wallet.android.presentation.common.OneOffEvent import com.babylon.wallet.android.presentation.common.OneOffEventHandler import com.babylon.wallet.android.presentation.common.OneOffEventHandlerImpl import com.babylon.wallet.android.presentation.common.StateViewModel import com.babylon.wallet.android.presentation.common.UiMessage import com.babylon.wallet.android.presentation.common.UiState -import com.radixdlt.sargon.RadixConnectMobile +import com.radixdlt.sargon.DappWalletInteractionErrorType import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import rdx.works.core.domain.DApp import rdx.works.core.then import rdx.works.profile.domain.GetProfileUseCase -import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -30,17 +32,28 @@ class MobileConnectLinkViewModel @Inject constructor( private val wellKnownDAppDefinitionRepository: WellKnownDAppDefinitionRepository, private val getProfileUseCase: GetProfileUseCase, private val getDAppsUseCase: GetDAppsUseCase, - private val radixConnectMobile: RadixConnectMobile, - private val incomingRequestRepository: IncomingRequestRepository + private val incomingRequestRepository: IncomingRequestRepository, + private val respondToIncomingRequestUseCase: RespondToIncomingRequestUseCase, + @ApplicationScope private val appScope: CoroutineScope ) : StateViewModel(), OneOffEventHandler by OneOffEventHandlerImpl() { private val args = MobileConnectArgs(savedStateHandle) + + private lateinit var request: IncomingMessage.IncomingRequest + override fun initialState(): State { return State() } init { viewModelScope.launch { + val requestToHandle = incomingRequestRepository.getRequest(args.interactionId) + if (requestToHandle == null) { + sendEvent(Event.Close) + return@launch + } else { + request = requestToHandle + } val developerMode = getProfileUseCase().appPreferences.security.isDeveloperModeEnabled _state.update { it.copy( @@ -48,9 +61,9 @@ class MobileConnectLinkViewModel @Inject constructor( isInDevMode = developerMode ) } - + val remoteId = request.remoteEntityId as? RadixMobileConnectRemoteSession ?: return@launch wellKnownDAppDefinitionRepository.getWellKnownDappDefinitions( - origin = args.request.origin.toString() + origin = remoteId.originVerificationUrl.toString() ).then { dAppDefinitions -> val dAppDefinition = dAppDefinitions.dAppDefinitions.firstOrNull() if (dAppDefinition != null) { @@ -76,20 +89,8 @@ class MobileConnectLinkViewModel @Inject constructor( _state.update { it.copy(isVerifying = true) } - - runCatching { - radixConnectMobile.requestOriginVerified(sessionId = args.request.sessionId) - args.request.interaction.toDomainModel( - remoteEntityId = RadixMobileConnectRemoteSession(id = args.request.sessionId.toString()) - ).getOrThrow() - }.onSuccess { request -> - incomingRequestRepository.add(request) - sendEvent(Event.Close) - }.onFailure { error -> - Timber.w(error) - _state.update { - it.copy(uiMessage = UiMessage.ErrorMessage(error), isVerifying = false) - } + viewModelScope.launch { + sendEvent(Event.HandleRequest(request)) } } @@ -97,21 +98,18 @@ class MobileConnectLinkViewModel @Inject constructor( _state.update { it.copy(isVerifying = true) } - - runCatching { - radixConnectMobile.requestOriginDenied(sessionId = args.request.sessionId) - }.onSuccess { + appScope.launch { + respondToIncomingRequestUseCase.respondWithFailure(request, DappWalletInteractionErrorType.REJECTED_BY_USER) + } + viewModelScope.launch { + incomingRequestRepository.requestHandled(args.interactionId) sendEvent(Event.Close) - }.onFailure { error -> - Timber.w(error) - _state.update { - it.copy(uiMessage = UiMessage.ErrorMessage(error), isVerifying = false) - } } } sealed class Event : OneOffEvent { data object Close : Event() + data class HandleRequest(val request: IncomingMessage.IncomingRequest) : Event() } data class State( diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/navigation/NavigationHost.kt b/app/src/main/java/com/babylon/wallet/android/presentation/navigation/NavigationHost.kt index 7a9e355ab7..8b9a4ff544 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/navigation/NavigationHost.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/navigation/NavigationHost.kt @@ -27,13 +27,16 @@ import com.babylon.wallet.android.presentation.account.settings.specificassets.s import com.babylon.wallet.android.presentation.account.settings.specificdepositor.specificDepositor import com.babylon.wallet.android.presentation.account.settings.thirdpartydeposits.accountThirdPartyDeposits import com.babylon.wallet.android.presentation.dapp.authorized.dappLoginAuthorizedNavGraph +import com.babylon.wallet.android.presentation.dapp.authorized.login.dAppLoginAuthorized import com.babylon.wallet.android.presentation.dapp.completion.ChooseAccountsCompletionScreen import com.babylon.wallet.android.presentation.dapp.unauthorized.dappLoginUnauthorizedNavGraph +import com.babylon.wallet.android.presentation.dapp.unauthorized.login.dAppLoginUnauthorized import com.babylon.wallet.android.presentation.incompatibleprofile.IncompatibleProfileScreen import com.babylon.wallet.android.presentation.incompatibleprofile.ROUTE_INCOMPATIBLE_PROFILE import com.babylon.wallet.android.presentation.main.MAIN_ROUTE import com.babylon.wallet.android.presentation.main.MainUiState import com.babylon.wallet.android.presentation.main.main +import com.babylon.wallet.android.presentation.mobileconnect.ROUTE_MOBILE_CONNECT import com.babylon.wallet.android.presentation.mobileconnect.mobileConnect import com.babylon.wallet.android.presentation.onboarding.OnboardingScreen import com.babylon.wallet.android.presentation.onboarding.cloudbackup.ConnectCloudBackupViewModel.ConnectMode @@ -71,6 +74,7 @@ import com.babylon.wallet.android.presentation.status.dapp.dAppDetailsDialog import com.babylon.wallet.android.presentation.status.dapp.dappInteractionDialog import com.babylon.wallet.android.presentation.status.transaction.transactionStatusDialog import com.babylon.wallet.android.presentation.survey.npsSurveyDialog +import com.babylon.wallet.android.presentation.transaction.transactionReview import com.babylon.wallet.android.presentation.transaction.transactionReviewScreen import com.babylon.wallet.android.presentation.transfer.transfer import com.babylon.wallet.android.presentation.transfer.transferScreen @@ -530,8 +534,25 @@ fun NavigationHost( navController.popBackStack() } ) - mobileConnect(onBackClick = { - navController.popBackStack() - }) + mobileConnect( + onBackClick = { + navController.popBackStack() + }, + onHandleRequestAuthorizedRequest = { + navController.dAppLoginAuthorized(it) { + popUpTo(ROUTE_MOBILE_CONNECT) { inclusive = true } + } + }, + onHandleUnauthorizedRequest = { + navController.dAppLoginUnauthorized(it) { + popUpTo(ROUTE_MOBILE_CONNECT) { inclusive = true } + } + }, + onHandleTransactionRequest = { + navController.transactionReview(it) { + popUpTo(ROUTE_MOBILE_CONNECT) { inclusive = true } + } + } + ) } } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/status/dapp/DappInteractionDialog.kt b/app/src/main/java/com/babylon/wallet/android/presentation/status/dapp/DappInteractionDialog.kt index 602b3fc464..5a8be664c0 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/status/dapp/DappInteractionDialog.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/status/dapp/DappInteractionDialog.kt @@ -73,7 +73,7 @@ private fun DappInteractionDialogContent( contentDescription = null ) Text( - text = stringResource(id = R.string.transactionStatus_success_title), + text = stringResource(id = R.string.dAppRequest_completion_title), style = RadixTheme.typography.title, color = RadixTheme.colors.gray1 ) @@ -85,8 +85,8 @@ private fun DappInteractionDialogContent( ) if (state.isMobileConnect) { Text( - text = "You can return back to the dApp!", - style = RadixTheme.typography.body1HighImportance, + text = stringResource(id = R.string.mobileConnect_interactionSuccess), + style = RadixTheme.typography.body1Regular, color = RadixTheme.colors.gray1, textAlign = TextAlign.Center ) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialog.kt b/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialog.kt index 66acf6d946..7bcb84887d 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialog.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialog.kt @@ -193,8 +193,8 @@ private fun SuccessContent( } if (isMobileConnect) { Text( - text = "You can return back to the dApp!", - style = RadixTheme.typography.body1HighImportance, + text = "Switch back to your browser to continue", + style = RadixTheme.typography.body1Regular, color = RadixTheme.colors.gray1, textAlign = TextAlign.Center ) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialogViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialogViewModel.kt index 4e735bca50..bda6a771cb 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialogViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/status/transaction/TransactionStatusDialogViewModel.kt @@ -94,7 +94,7 @@ class TransactionStatusDialogViewModel @Inject constructor( }.onFailure { error -> if (!status.isInternal) { (error as? RadixWalletException.TransactionSubmitException)?.let { exception -> - incomingRequestRepository.getTransactionWriteRequest(status.requestId)?.let { transactionRequest -> + incomingRequestRepository.getRequest(status.requestId)?.let { transactionRequest -> respondToIncomingRequestUseCase.respondWithFailure( request = transactionRequest, error = exception.ceError, diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewNav.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewNav.kt index 8cbab7e998..63d034ad80 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewNav.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewNav.kt @@ -5,6 +5,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument @@ -18,14 +19,14 @@ internal const val ARG_TRANSACTION_REQUEST_ID = "arg_transaction_request_id" const val ROUTE_TRANSACTION_REVIEW = "transaction_review_route/{$ARG_TRANSACTION_REQUEST_ID}" -internal class TransactionReviewArgs(val requestId: String) { +internal class TransactionReviewArgs(val interactionId: String) { constructor(savedStateHandle: SavedStateHandle) : this( checkNotNull(savedStateHandle[ARG_TRANSACTION_REQUEST_ID]) as String ) } -fun NavController.transactionReview(requestId: String) { - navigate("transaction_review_route/$requestId") +fun NavController.transactionReview(requestId: String, navOptionsBuilder: NavOptionsBuilder.() -> Unit = {}) { + navigate("transaction_review_route/$requestId", navOptionsBuilder) } fun NavGraphBuilder.transactionReviewScreen( diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewScreen.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewScreen.kt index 46b1853130..f3ba21f45c 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewScreen.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewScreen.kt @@ -85,7 +85,6 @@ fun TransactionReviewScreen( ) { val state by viewModel.state.collectAsStateWithLifecycle() val context = LocalContext.current - TransactionPreviewContent( onBackClick = viewModel::onBackClick, state = state, @@ -139,10 +138,11 @@ fun TransactionReviewScreen( ) } } - - LaunchedEffect(state.isTransactionDismissed) { - if (state.isTransactionDismissed) { - onDismiss() + LaunchedEffect(Unit) { + viewModel.oneOffEvent.collect { event -> + when (event) { + Event.Dismiss -> onDismiss() + } } } } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModel.kt index 4143e9f7ee..cb286e0b5b 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModel.kt @@ -15,6 +15,9 @@ import com.babylon.wallet.android.domain.model.Transferable import com.babylon.wallet.android.domain.model.TransferableAsset import com.babylon.wallet.android.domain.usecases.GetDAppsUseCase import com.babylon.wallet.android.domain.usecases.SignTransactionUseCase +import com.babylon.wallet.android.presentation.common.OneOffEvent +import com.babylon.wallet.android.presentation.common.OneOffEventHandler +import com.babylon.wallet.android.presentation.common.OneOffEventHandlerImpl import com.babylon.wallet.android.presentation.common.StateViewModel import com.babylon.wallet.android.presentation.common.UiMessage import com.babylon.wallet.android.presentation.common.UiState @@ -24,6 +27,8 @@ import com.babylon.wallet.android.presentation.transaction.fees.TransactionFees import com.babylon.wallet.android.presentation.transaction.fees.TransactionFeesDelegate import com.babylon.wallet.android.presentation.transaction.guarantees.TransactionGuaranteesDelegate import com.babylon.wallet.android.presentation.transaction.submit.TransactionSubmitDelegate +import com.babylon.wallet.android.utils.AppEvent +import com.babylon.wallet.android.utils.AppEventBus import com.radixdlt.sargon.Account import com.radixdlt.sargon.AccountAddress import com.radixdlt.sargon.Address @@ -45,6 +50,7 @@ import com.radixdlt.sargon.extensions.times import com.radixdlt.sargon.extensions.toDecimal192 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import rdx.works.core.domain.DApp @@ -60,15 +66,16 @@ import javax.inject.Inject @Suppress("LongParameterList", "TooManyFunctions") @HiltViewModel class TransactionReviewViewModel @Inject constructor( + private val appEventBus: AppEventBus, private val signTransactionUseCase: SignTransactionUseCase, private val analysis: TransactionAnalysisDelegate, private val guarantees: TransactionGuaranteesDelegate, private val fees: TransactionFeesDelegate, private val submit: TransactionSubmitDelegate, private val getDAppsUseCase: GetDAppsUseCase, - incomingRequestRepository: IncomingRequestRepository, + private val incomingRequestRepository: IncomingRequestRepository, savedStateHandle: SavedStateHandle, -) : StateViewModel() { +) : StateViewModel(), OneOffEventHandler by OneOffEventHandlerImpl() { private val args = TransactionReviewArgs(savedStateHandle) @@ -83,13 +90,12 @@ class TransactionReviewViewModel @Inject constructor( guarantees(scope = viewModelScope, state = _state) fees(scope = viewModelScope, state = _state) submit(scope = viewModelScope, state = _state) + submit.oneOffEventHandler = this - val request = incomingRequestRepository.getTransactionWriteRequest(args.requestId) + val request = incomingRequestRepository.getRequest(args.interactionId) as? IncomingMessage.IncomingRequest.TransactionRequest if (request == null) { viewModelScope.launch { - _state.update { state -> - state.copy(isTransactionDismissed = true) - } + sendEvent(Event.Dismiss) } } else { _state.update { it.copy(request = request) } @@ -112,6 +118,14 @@ class TransactionReviewViewModel @Inject constructor( } } } + viewModelScope.launch { + appEventBus.events.filterIsInstance().collect { + if (it.interactionId == args.interactionId) { + sendEvent(Event.Dismiss) + incomingRequestRepository.requestDeferred(args.interactionId) + } + } + } } fun onBackClick() { @@ -250,8 +264,7 @@ class TransactionReviewViewModel @Inject constructor( private val latestFeesMode: Sheet.CustomizeFees.FeesMode = Sheet.CustomizeFees.FeesMode.Default, val error: TransactionErrorMessage? = null, val ephemeralNotaryPrivateKey: Curve25519SecretKey = Curve25519SecretKey.secureRandom(), - val interactionState: InteractionState? = null, - val isTransactionDismissed: Boolean = false + val interactionState: InteractionState? = null ) : UiState { val requestNonNull: IncomingMessage.IncomingRequest.TransactionRequest @@ -421,6 +434,10 @@ class TransactionReviewViewModel @Inject constructor( } } +sealed interface Event : OneOffEvent { + data object Dismiss : Event +} + data class TransactionErrorMessage( private val error: Throwable? ) { diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/submit/TransactionSubmitDelegate.kt b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/submit/TransactionSubmitDelegate.kt index 86e08df470..45932d5374 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/transaction/submit/TransactionSubmitDelegate.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/transaction/submit/TransactionSubmitDelegate.kt @@ -13,7 +13,9 @@ import com.babylon.wallet.android.domain.toConnectorExtensionError import com.babylon.wallet.android.domain.usecases.RespondToIncomingRequestUseCase import com.babylon.wallet.android.domain.usecases.SignTransactionUseCase import com.babylon.wallet.android.domain.usecases.transaction.SubmitTransactionUseCase +import com.babylon.wallet.android.presentation.common.OneOffEventHandler import com.babylon.wallet.android.presentation.common.ViewModelDelegate +import com.babylon.wallet.android.presentation.transaction.Event import com.babylon.wallet.android.presentation.transaction.PreviewType import com.babylon.wallet.android.presentation.transaction.TransactionErrorMessage import com.babylon.wallet.android.presentation.transaction.TransactionReviewViewModel @@ -52,6 +54,8 @@ class TransactionSubmitDelegate @Inject constructor( private var approvalJob: Job? = null + var oneOffEventHandler: OneOffEventHandler? = null + @Suppress("SwallowedException") fun onSubmit( signTransactionUseCase: SignTransactionUseCase, @@ -97,7 +101,7 @@ class TransactionSubmitDelegate @Inject constructor( suspend fun onDismiss( signTransactionUseCase: SignTransactionUseCase, exception: RadixWalletException.DappRequestException - ) { + ): Result = runCatching { if (approvalJob == null) { val request = _state.value.requestNonNull if (!request.isInternal) { @@ -107,10 +111,8 @@ class TransactionSubmitDelegate @Inject constructor( message = exception.getDappMessage() ) } - _state.update { - it.copy(isTransactionDismissed = true) - } - incomingRequestRepository.requestHandled(request.interactionId.toString()) + oneOffEventHandler?.sendEvent(Event.Dismiss) + incomingRequestRepository.requestHandled(request.interactionId) } else if (_state.value.interactionState != null) { approvalJob?.cancel() approvalJob = null diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/ui/composables/Dialogs.kt b/app/src/main/java/com/babylon/wallet/android/presentation/ui/composables/Dialogs.kt index 5e9cb8068e..2dbe527aa4 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/ui/composables/Dialogs.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/ui/composables/Dialogs.kt @@ -484,8 +484,8 @@ fun FailureDialogContent( } if (isMobileConnect) { Text( - text = "You can return back to the dApp!", - style = RadixTheme.typography.body1HighImportance, + text = stringResource(id = R.string.mobileConnect_interactionSuccess), + style = RadixTheme.typography.body1Regular, color = RadixTheme.colors.gray1, textAlign = TextAlign.Center ) diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletScreen.kt b/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletScreen.kt index 420cef4886..36129b451b 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletScreen.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -104,6 +105,13 @@ fun WalletScreen( onRadixBannerDismiss = viewModel::onRadixBannerDismiss ) + val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState() + LaunchedEffect(lifecycleState) { + if (lifecycleState == Lifecycle.State.RESUMED) { + viewModel.processBufferedDeepLinkRequest() + } + } + LaunchedEffect(Unit) { viewModel.babylonFactorSourceDoesNotExistEvent.collect { viewModel.createBabylonFactorSource { context.biometricAuthenticateSuspend() } diff --git a/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletViewModel.kt b/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletViewModel.kt index 20db65c0e9..5309e2a9db 100644 --- a/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletViewModel.kt +++ b/app/src/main/java/com/babylon/wallet/android/presentation/wallet/WalletViewModel.kt @@ -119,6 +119,12 @@ class WalletViewModel @Inject constructor( checkForOldBackupSystemToMigrate() } + fun processBufferedDeepLinkRequest() { + viewModelScope.launch { + appEventBus.sendEvent(AppEvent.ProcessBufferedDeepLinkRequest) + } + } + override fun initialState() = WalletUiState() fun popUpScreen(): StateFlow = popUpScreen diff --git a/app/src/main/java/com/babylon/wallet/android/utils/AppEventBusImpl.kt b/app/src/main/java/com/babylon/wallet/android/utils/AppEventBusImpl.kt index a7ac91bb20..e3fe5b093c 100644 --- a/app/src/main/java/com/babylon/wallet/android/utils/AppEventBusImpl.kt +++ b/app/src/main/java/com/babylon/wallet/android/utils/AppEventBusImpl.kt @@ -33,6 +33,8 @@ sealed interface AppEvent { data object NPSSurveySubmitted : AppEvent data object SecureFolderWarning : AppEvent + data class DeferRequestHandling(val interactionId: String) : AppEvent + data object ProcessBufferedDeepLinkRequest : AppEvent sealed interface AccessFactorSources : AppEvent { data class SelectedLedgerDevice(val ledgerFactorSource: FactorSource.Ledger) : AccessFactorSources diff --git a/app/src/main/java/com/babylon/wallet/android/utils/ContextExtensions.kt b/app/src/main/java/com/babylon/wallet/android/utils/ContextExtensions.kt index 4b92a0abe5..92676dbc0d 100644 --- a/app/src/main/java/com/babylon/wallet/android/utils/ContextExtensions.kt +++ b/app/src/main/java/com/babylon/wallet/android/utils/ContextExtensions.kt @@ -80,7 +80,7 @@ fun Context.openEmail(recipientAddress: String? = null, subject: String? = null, } catch (activityNotFound: ActivityNotFoundException) { Toast.makeText( this, - "No email client installed", // TODO crowdin + getString(R.string.no_email_client_installed), Toast.LENGTH_SHORT ).show() } diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index d0a802110b..64dba9d6ec 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1,1213 +1,1215 @@ - Please write down seed phrase to ensure Account control - Seed phrase required - begin entry - I have written down this seed phrase - Create a New Account - Legacy - Welcome. Here are all your Accounts on the Radix Network. - Radix Wallet - Total value - dApp Definition - Legacy - Legacy (Ledger) - Ledger - Visit the Radix Dashboard - Ready to get started using the Radix Network and your Wallet? - SERIOUS ERROR - PLEASE READ - Your wallet is in a rare condition that must be resolved manually. Please email support at hello@radixdlt.com with subject line BDFS ERROR. Somebody will respond and help you resolve the issue safely. - Affected Accounts - Affected Personas - OK (%d) - Start Using Radix - Complete setting up your wallet and start staking, using dApps and more! - Get Started Now - Your wallet has encountered a problem that should be resolved before you continue use. If you have a Samsung phone, this may be caused by putting the Radix Wallet in the \"Secure Folder\". Please contact support at hello@radixdlt.com for assistance. - Transfer - Tokens - NFTs - Staking - Radix Network XRD Stake Summary - STAKED VALIDATORS (%d) - Unstaking - Ready to Claim - Ready to be claimed - Staked - Current Stake: %s - Liquid Stake Units - Stake Claim NFTs - WORTH - Claim - Pool Units - Unknown - Unknown - Unknown - Current Redeemable Value - Missing Total supply - could not calculate redemption value - Badges - Address - Validator - Name - Current Supply - Unknown - Behavior - Ready to claim in about %d minutes or less. - Tags - Official Radix - Associated dApps - You have no Tokens - What are Tokens? - You have no NFTs - What are NFTs? - ID - Name - Description - complex data - Name - %d NFTs - %d NFTs of total supply %d - You have no Stakes - What is Staking? - You have no Pool units - What are Pool units? - You have no badges - What are badges? - Hide Asset - Hide this asset in your Radix Wallet? You can always unhide it in your account settings. - Hide Asset - This is a simple asset - The supply of this asset can be increased. - The supply of this asset can be decreased. - The supply of this asset can be increased or decreased. - Only the Radix Network may increase or decrease the supply of XRD. - Anyone can increase the supply of this asset. - Anyone can decrease the supply of this asset. - Anyone can increase or decrease the supply of this asset. - Movement of this asset is restricted. - Movement of this asset can be restricted in the future. - Anyone can restrict movement of this token in the future. - Naming and information about this asset can be changed. - Anyone can change naming and information about this asset. - A third party can remove this asset from accounts and dApps. - Anyone can remove this asset from accounts and dApps. - A third party can freeze this asset in place. - Anyone can freeze this asset in place. - Data that is set on these NFTs can be changed. - Anyone can change data that is set on these NFTs. - Account Settings - Personalize this Account - Account Label - Account Color - Select from a list of unique colors - Show Assets with Tags - Show Account QR Code - Updated - Select which tags to show for assets in this Account - Set how you want this Account to work - Third-party Deposits - Hide Account - Account Hidden - Get XRD Test Tokens - This may take several seconds, please wait for completion - Rename Account - Enter a new label for this Account - Update - Select the color for this Account - Selected - Hide This Account - Hide this Account in your wallet? You can always unhide it from the main application settings. - Hide Account - Choose if you want to allow third parties to directly deposit assets into your Account. Deposits that you approve yourself in your Radix Wallet are always accepted. - Accept all deposits - Allow third-parties to deposit any asset - Only accept known - Allow third-parties to deposit only assets this Account already holds - Deny all - Deny all third-party deposits - This account will not be able to receive \"air drops\" or be used by a trusted contact to assist with account recovery. - Discard Changes - Keep Editing - Are you sure you want to discard changes? - Allow/Deny specific assets - Deny or allow third-party deposits of specific assets, ignoring the setting above - Add a Depositor Badge - Enter the badge’s resource address (starting with “reso”) - Allow specific depositors - Allow certain third party depositors to deposit assets freely - Add Depositor Badge - Remove Asset - The asset will be removed from the deny list - The asset will be removed from the allow list - The badge will be removed from the list - Remove Depositor - The depositor will be removed from the allow list - Allow/Deny Specific Assets - Update - Allow - Deny - Add a specific asset by its resource address to allow all third-party deposits - Add a specific asset by its resource address to deny all third-party deposits - Add an Asset - Enter the asset’s resource address (starting with “reso”) - Resource Address - Allow Deposits - Deny Deposits - Add Asset - The following resource addresses may always be deposited to this account by third parties. - The following resource addresses may never be deposited to this account by third parties. - Add a specific badge by its resource address to allow all deposits from its holder. - The holder of the following badges may always deposit accounts to this account. - Select exception list - Sorry, this Account\'s third-party exceptions and depositor lists are in an unknown state and cannot be viewed or edited because it was imported using only a seed phrase or Ledger. A forthcoming wallet update will enable viewing and editing of these lists. - Asset creators can add tags to them. You can choose which tags you want to see in this Account. - Select the ones you’d like shown on all your assets. - Recommended - Set development preferences - Dev Preferences - Hide This Account - Are you sure you want to hide this account? - Settings - Authorized dApps - Personas - Account Security & Settings - App Settings - Please write down the seed phrase for your Personas - Link your Wallet to a Desktop Browser - Scan the QR code in the Radix Wallet Connector extension - Link to Connector - Radix Olympia Desktop Wallet user? - Get started importing your Olympia accounts into your new Radix Wallet - Import Legacy Accounts - Version: %s build #%s - Linked Connectors - Connect your Radix Wallet to desktop web browsers by linking to the Radix Connector browser extension. Here are your linked Connectors. - Last connected %s - Link New Connector - Link New Connector - Open your Radix Connector extension\'s menu by clicking its icon in your list of browser extensions, and scan the QR code shown. - Linking… - Name New Connector - What would you like to call this Radix Connector installation? - e.g. Chrome on Personal Laptop - Name this connector e.g. ‘Chrome on MacBook Pro’ - Continue - Remove Connection - You will no longer be able to connect your wallet to this device and browser combination. - Remove - Access Required - Camera access is required to link to a Connector. - Access Required - Local network access is required to link to a Connector. - Incorrect QR code scanned. - Please scan the QR code provided by your Radix Wallet Connector browser extension. - Link Connector - Update Link - This Connector will be trusted to verify the dApp origin of requests to this wallet.\n\nOnly continue if you are linking to the **official Radix Connector browser extension** - or a Connector you control and trust. - This appears to be a Radix Connector you previously linked to. Link will be updated. - Link Failed - This type of Connector link is not supported. - Changing a Connector’s type is not supported. - This is an old version of the Radix Connector browser extension. Please update to the latest Connector and try linking again. - Re-link Connector - Radix Connector now supports linking multiple phones with one browser.\n\nTo support this feature, we\'ve had to disconnect your existing links – **please re-link your Connector(s).** - Any Connectors you had linked to this wallet using a different phone have been disconnected\n\n**Please re-link your Connector(s) to use with this phone.** - Later - Gateways - Choose the gateway your wallet will use to connect to the Radix Network or test networks. Only change this if you know what you’re doing. - What is a Gateway? - RCnet Gateway - Add New Gateway - Add New Gateway - Enter a gateway URL - Enter full URL - Add Gateway - This gateway is already added - No gateway found at specified URL - There was an error establishing a connection - Remove Gateway - You will no longer be able to connect to this gateway. - Authorized dApps - These are the dApps that you have logged into using the Radix Wallet. - What is a dApp? - dApp Definition - Missing description - Website - Associated Tokens - Unknown name - Associated NFTs - Here are the Personas that you have used to login to this dApp. - No Personas have been used to login to this dApp. - Forget this dApp - Persona Hidden - Edit Avatar - Persona Label - Here is the personal data that you are sharing with %s. - You are not sharing any personal data with %s. - First Name - Last Name - Email Address - Phone Number - Edit Persona - Here are the dApps you have logged into with this Persona. - Here are the Account names and addresses that you are currently sharing with %s. - Edit Account Sharing - Disconnect Persona from this dApp - Forget This dApp - Do you really want to forget this dApp and remove its permissions for all Personas? - Forget dApp? - Remove Authorization - This dApp will no longer have authorization to see data associated with this Persona, unless you choose to login with it again in the future. - Continue - Full Name - Name Order - Given Name(s) - Family Name - Nickname - Eastern style (family name first) - Western style (given name(s) first) - Hide This Persona - Are you sure you want to hide this persona? - Required by dApp - The following information can be seen if requested by the dApp - Label cannot be blank - Invalid email address - Required field for this dApp - Add a Field - Add a Field - Choose one or more data fields to add to this Persona. - Add Data Fields - Discard Changes - Keep Editing - Are you sure you want to discard changes to this Persona? - Personas - Here are all of your current Personas in your Radix Wallet. - What is a Persona? - Create a New Persona - Write down main seed phrase - Your Account lives on the Radix Network and you can access it any time in your Radix Wallet. - You’ve created your first Account! - Your Account has been created. - e.g. My Main Account - What would you like to call your Account? - This can be changed any time. - Continue - Create an Account - Create First Account - Create New Account - Choose Accounts - Persona Selection - Gateways - Account List - Persona List - Continue to %s - Congratulations - Create Ledger Account - Create Ledger Persona - Your Account lives on the Radix Network and you can access it any time in your Wallet. - Create with Ledger Hardware Wallet - You will be asked to sign transactions with the Ledger device you select. - Empty display name - You’ve created your first Persona! - Your Persona has been created. - Personal data that you add to your Persona will only be shared with dApps with your permission. - Some dApps may request personal information, like name or email address, that can be added to your Persona. Add some data now if you like. - This will be shared with dApps you login to - Create a Persona - A Persona is an identity that you own and control. You can have as many as you like. - Personas are used to login to dApps on Radix. dApps may request access to personal information associated with your Persona, like your name or email address. - Learn about Personas - Continue - What would you like to call your Persona? - e.g. My Main Persona - Continue - Required field - Save and Continue - Login Request - %s is requesting that you login with a Persona. - New Login Request - %s is requesting that you login for the **first time** with a Persona. - Choose a Persona - Your last login was on %s - Continue - Account Permission - %d or more accounts - Any number of accounts - %d accounts - 1 account - Continue - **%s** is requesting permission to *always* be able to view Account information when you login with this Persona. - You can update this permission in wallet settings for this dApp at any time. - Create a New Account - You are now connected to %s. You can change your preferences for this dApp in wallet settings at any time. - dApp Connection Successful - DApp error - Continue - Account Request - **%s** is making a one-time request for at least %d accounts. - **%s** is making a one-time request for any number of accounts. - **%s** is making a one-time request for at least 1 account. - **%s** is making a one-time request for %d accounts. - **%s** is making a one-time request for 1 account. - Account Permission - Choose at least %d accounts you wish to use with **%s**. - Choose any accounts you wish to use with **%s**. - Choose at least 1 account you wish to use with **%s**. - Choose %d accounts you wish to use with **%s**. - Choose 1 account you wish to use with **%s**. - One-Time Data Request - Choose the data to provide - **%s** is requesting that you provide some pieces of personal data **just one time** - Continue - Personal Data Permission - **%s** is requesting permission to **always** be able to view the following personal data when you login with this Persona. - You can update this permission in your Settings at any time. - Continue - Invalid content - Incompatible connector extension - Network mismatch - Invalid value of `numberOfAccountsInvalid`: must not be `exactly(0)` nor can `quantity` be negative - %s (CE: %s, wallet: %s) - \'%s\' is not valid origin. - \'%s\' is not valid account address. - Invalid data in request - Please update Radix Wallet - Please update Radix Connector browser extension - Invalid origin - Invalid dApp Definition Address - Radix Connect connection error - Invalid Request. - Could not validate the dApp. - Request received from dApp is invalid. - dApp specified an invalid Persona. - dApp made a request intended for network %s, but you are currently connected to %s. - Failed to send request response to dApp. - Success - Request from %s complete - Danger! Bad dApp configuration, or you\'re being spoofed! - Loading… - Unknown dApp - Radix Wallet - Edit - Required information: - Review Your Transaction - Proposed by %s - Review Your Transfer - Approve - Customize Guarantees - Estimated - Account - Guaranteed - Raw Transaction - Unknown - Unnamed dApp - Worth - To be claimed - Unknown pool - %d Unknown Components - %d Pool Components - Pool Units - Slide to Sign - %s XRD - Not enough XRD for transaction fee - Fee payer account required - Withdrawing From - Depositing To - Sending to - Message - Presenting - Using dApps - Third-party deposit setting - Third-party deposit exceptions - Staking to Validators - Requesting unstake from validators - Claim from validators - Contributing to pools - Redeeming from pools - Transaction Fee - The network is currently congested. Add a tip to speed up your transfer. - Customize - Customize Guarantees - Protect yourself by setting guaranteed minimums for estimated deposits - Apply - How do guarantees work? - Set guaranteed minimum %% - Preparing Transaction - Preparing transaction for signing - Submitting - Submitted but not confirmed - Rejected - Failed - Successfully committed - Transaction ID - Status - Submitting Transaction - Review New Deposit Settings - Third-party deposit setting - Allow third parties to deposit **any asset** to this account. - Allow third parties to deposit **only assets this account has already held**. - **Disallow** all deposits from third parties without your consent. - Allow - Disallow - Remove Exception - Add Depositor - Remove Depositor - A proposed transaction was rejected because it contains one or more reserved instructions. - Could Not Complete - The required seed phrase is missing. Please return to the account and begin the recovery process. - Warning - This is a complex transaction that cannot be summarized - the raw transaction manifest will be shown. Do not submit unless you understand the contents. - Completing Transaction… - Transaction ID: - Transaction Success - Transaction Failed - Transaction Rejected - Transaction Error - Your transaction was successful - Your transaction was processed, but had a problem that caused it to fail permanently - This transaction was rejected and is unlikely to be processed, but could potentially be processed within the next %s minutes. It is likely that the dApp you are using proposed a transaction that includes an action that is not currently valid. - Your transaction was improperly constructed and cannot be processed - Something Went Wrong - Transaction was rejected as invalid by the Radix Network. - Stop waiting for transaction result? The transaction will not be canceled. - Dismiss - This transaction requires to be completed - A guarantee on transaction results was not met. Consider reducing your preferred guarantee %% - Copy Address - Copied to Clipboard - Copy NFT ID - Copy Transaction ID - There is no web browser installed in this device - View on Radix Dashboard - Show Address QR Code - Verify Address with Ledger - Address verified - Verify address request failed - QR code for an account - Could not create QR code - Authenticate to continue - Authenticate to create new %s with this phone. - Authenticate to sign proof with this phone. - Authenticate to sign transaction with this phone. - Display seed phrase. - Create Auth signing key. - Check if seed phrase already exists. - Checking accounts. - Update account metadata. - Unsecured Device - Your device currently has no device access security set, such as biometrics or a PIN. The Radix Wallet requires this to be set for your security. - Open Settings - Quit - Account - Cancel - Continue - Connected to a test network, not Radix main network. - An Error Occurred - None - OK - Done - Copy - Persona - History - Pool - Retry - Remove - Something Went Wrong - Settings - Confirm - Save - Invalid - Public - Choose - Max - Optional - Show More - Show Less - Component - Unauthorized - Gateway access blocked due to exceeding rate limit. Please wait a few minutes to retry. - Bad HTTP response status code %d - Dismiss - Invalid Persona specified by dApp - Invalid request - Failed to import Radix Wallet backup: %s - Failed to import Radix Wallet backup, error: %s, version: %s - Failed to commit transaction - Failed to convert transaction manifest - Failed to get epoch - Failed to build transaction header - Failed to convert transaction manifest - You don\'t have access to some accounts or personas required to authorise this transaction - Wrong network - No funds to approve transaction - Failed to poll transaction status - Failed to prepare transaction - Transaction rejected - Unknown error - Failed to find ledger - Failed to add Transaction Fee, try a different amount of fee payer. - Failed to add Guarantee, try a different percentage, or try skip adding a guarantee. - A proposed transaction could not be processed. - Your current Ledger settings only allow signing of simple token transfers. Please either enable \"verbose mode\" (to see full transaction manifests) or \"blind signing mode\" (to enable signing of complex transaction manifest hashes) on your Ledger app device. - Failed to convert transaction manifest - Failed to submit transaction - Persona label too long - Account label too long - Account label required - One of the receiving accounts does not allow Third-Party deposits - Email Support - Please email support to automatically provide debugging info, and get assistance. + Please write down seed phrase to ensure Account control + Seed phrase required - begin entry + I have written down this seed phrase + Create a New Account + Legacy + Welcome. Here are all your Accounts on the Radix Network. + Radix Wallet + Total value + dApp Definition + Legacy + Legacy (Ledger) + Ledger + Visit the Radix Dashboard + Ready to get started using the Radix Network and your Wallet? + SERIOUS ERROR - PLEASE READ + Your wallet is in a rare condition that must be resolved manually. Please email support at hello@radixdlt.com with subject line BDFS ERROR. Somebody will respond and help you resolve the issue safely. + Affected Accounts + Affected Personas + OK (%d) + Start Using Radix + Complete setting up your wallet and start staking, using dApps and more! + Get Started Now + Your wallet has encountered a problem that should be resolved before you continue use. If you have a Samsung phone, this may be caused by putting the Radix Wallet in the \"Secure Folder\". Please contact support at hello@radixdlt.com for assistance. + Transfer + Tokens + NFTs + Staking + Radix Network XRD Stake Summary + STAKED VALIDATORS (%d) + Unstaking + Ready to Claim + Ready to be claimed + Staked + Current Stake: %s + Liquid Stake Units + Stake Claim NFTs + WORTH + Claim + Pool Units + Unknown + Unknown + Unknown + Current Redeemable Value + Missing Total supply - could not calculate redemption value + Badges + Address + Validator + Name + Current Supply + Unknown + Behavior + Ready to claim in about %d minutes or less. + Tags + Official Radix + Associated dApps + You have no Tokens + What are Tokens? + You have no NFTs + What are NFTs? + ID + Name + Description + complex data + Name + %d NFTs + %d NFTs of total supply %d + You have no Stakes + What is Staking? + You have no Pool units + What are Pool units? + You have no badges + What are badges? + Hide Asset + Hide this asset in your Radix Wallet? You can always unhide it in your account settings. + Hide Asset + This is a simple asset + The supply of this asset can be increased. + The supply of this asset can be decreased. + The supply of this asset can be increased or decreased. + Only the Radix Network may increase or decrease the supply of XRD. + Anyone can increase the supply of this asset. + Anyone can decrease the supply of this asset. + Anyone can increase or decrease the supply of this asset. + Movement of this asset is restricted. + Movement of this asset can be restricted in the future. + Anyone can restrict movement of this token in the future. + Naming and information about this asset can be changed. + Anyone can change naming and information about this asset. + A third party can remove this asset from accounts and dApps. + Anyone can remove this asset from accounts and dApps. + A third party can freeze this asset in place. + Anyone can freeze this asset in place. + Data that is set on these NFTs can be changed. + Anyone can change data that is set on these NFTs. + Account Settings + Personalize this Account + Account Label + Account Color + Select from a list of unique colors + Show Assets with Tags + Show Account QR Code + Updated + Select which tags to show for assets in this Account + Set how you want this Account to work + Third-party Deposits + Hide Account + Account Hidden + Get XRD Test Tokens + This may take several seconds, please wait for completion + Rename Account + Enter a new label for this Account + Update + Select the color for this Account + Selected + Hide This Account + Hide this Account in your wallet? You can always unhide it from the main application settings. + Hide Account + Choose if you want to allow third parties to directly deposit assets into your Account. Deposits that you approve yourself in your Radix Wallet are always accepted. + Accept all deposits + Allow third-parties to deposit any asset + Only accept known + Allow third-parties to deposit only assets this Account already holds + Deny all + Deny all third-party deposits + This account will not be able to receive \"air drops\" or be used by a trusted contact to assist with account recovery. + Discard Changes + Keep Editing + Are you sure you want to discard changes? + Allow/Deny specific assets + Deny or allow third-party deposits of specific assets, ignoring the setting above + Add a Depositor Badge + Enter the badge’s resource address (starting with “reso”) + Allow specific depositors + Allow certain third party depositors to deposit assets freely + Add Depositor Badge + Remove Asset + The asset will be removed from the deny list + The asset will be removed from the allow list + The badge will be removed from the list + Remove Depositor + The depositor will be removed from the allow list + Allow/Deny Specific Assets + Update + Allow + Deny + Add a specific asset by its resource address to allow all third-party deposits + Add a specific asset by its resource address to deny all third-party deposits + Add an Asset + Enter the asset’s resource address (starting with “reso”) + Resource Address + Allow Deposits + Deny Deposits + Add Asset + The following resource addresses may always be deposited to this account by third parties. + The following resource addresses may never be deposited to this account by third parties. + Add a specific badge by its resource address to allow all deposits from its holder. + The holder of the following badges may always deposit accounts to this account. + Select exception list + Sorry, this Account\'s third-party exceptions and depositor lists are in an unknown state and cannot be viewed or edited because it was imported using only a seed phrase or Ledger. A forthcoming wallet update will enable viewing and editing of these lists. + Asset creators can add tags to them. You can choose which tags you want to see in this Account. + Select the ones you’d like shown on all your assets. + Recommended + Set development preferences + Dev Preferences + Hide This Account + Are you sure you want to hide this account? + Settings + Authorized dApps + Personas + Account Security & Settings + App Settings + Please write down the seed phrase for your Personas + Link your Wallet to a Desktop Browser + Scan the QR code in the Radix Wallet Connector extension + Link to Connector + Radix Olympia Desktop Wallet user? + Get started importing your Olympia accounts into your new Radix Wallet + Import Legacy Accounts + Version: %s build #%s + Linked Connectors + Connect your Radix Wallet to desktop web browsers by linking to the Radix Connector browser extension. Here are your linked Connectors. + Last connected %s + Link New Connector + Link New Connector + Open your Radix Connector extension\'s menu by clicking its icon in your list of browser extensions, and scan the QR code shown. + Linking… + Name New Connector + What would you like to call this Radix Connector installation? + e.g. Chrome on Personal Laptop + Name this connector e.g. ‘Chrome on MacBook Pro’ + Continue + Remove Connection + You will no longer be able to connect your wallet to this device and browser combination. + Remove + Access Required + Camera access is required to link to a Connector. + Access Required + Local network access is required to link to a Connector. + Incorrect QR code scanned. + Please scan the QR code provided by your Radix Wallet Connector browser extension. + Link Connector + Update Link + This Connector will be trusted to verify the dApp origin of requests to this wallet.\n\nOnly continue if you are linking to the **official Radix Connector browser extension** - or a Connector you control and trust. + This appears to be a Radix Connector you previously linked to. Link will be updated. + Link Failed + This type of Connector link is not supported. + Changing a Connector’s type is not supported. + This is an old version of the Radix Connector browser extension. Please update to the latest Connector and try linking again. + Re-link Connector + Radix Connector now supports linking multiple phones with one browser.\n\nTo support this feature, we\'ve had to disconnect your existing links – **please re-link your Connector(s).** + Any Connectors you had linked to this wallet using a different phone have been disconnected\n\n**Please re-link your Connector(s) to use with this phone.** + Later + Gateways + Choose the gateway your wallet will use to connect to the Radix Network or test networks. Only change this if you know what you’re doing. + What is a Gateway? + RCnet Gateway + Add New Gateway + Add New Gateway + Enter a gateway URL + Enter full URL + Add Gateway + This gateway is already added + No gateway found at specified URL + There was an error establishing a connection + Remove Gateway + You will no longer be able to connect to this gateway. + Authorized dApps + These are the dApps that you have logged into using the Radix Wallet. + What is a dApp? + dApp Definition + Missing description + Website + Associated Tokens + Unknown name + Associated NFTs + Here are the Personas that you have used to login to this dApp. + No Personas have been used to login to this dApp. + Forget this dApp + Persona Hidden + Edit Avatar + Persona Label + Here is the personal data that you are sharing with %s. + You are not sharing any personal data with %s. + First Name + Last Name + Email Address + Phone Number + Edit Persona + Here are the dApps you have logged into with this Persona. + Here are the Account names and addresses that you are currently sharing with %s. + Edit Account Sharing + Disconnect Persona from this dApp + Forget This dApp + Do you really want to forget this dApp and remove its permissions for all Personas? + Forget dApp? + Remove Authorization + This dApp will no longer have authorization to see data associated with this Persona, unless you choose to login with it again in the future. + Continue + Full Name + Name Order + Given Name(s) + Family Name + Nickname + Eastern style (family name first) + Western style (given name(s) first) + Hide This Persona + Are you sure you want to hide this persona? + Required by dApp + The following information can be seen if requested by the dApp + Label cannot be blank + Invalid email address + Required field for this dApp + Add a Field + Add a Field + Choose one or more data fields to add to this Persona. + Add Data Fields + Discard Changes + Keep Editing + Are you sure you want to discard changes to this Persona? + Personas + Here are all of your current Personas in your Radix Wallet. + What is a Persona? + Create a New Persona + Write down main seed phrase + Your Account lives on the Radix Network and you can access it any time in your Radix Wallet. + You’ve created your first Account! + Your Account has been created. + e.g. My Main Account + What would you like to call your Account? + This can be changed any time. + Continue + Create an Account + Create First Account + Create New Account + Choose Accounts + Persona Selection + Gateways + Account List + Persona List + Continue to %s + Congratulations + Create Ledger Account + Create Ledger Persona + Your Account lives on the Radix Network and you can access it any time in your Wallet. + Create with Ledger Hardware Wallet + You will be asked to sign transactions with the Ledger device you select. + Empty display name + You’ve created your first Persona! + Your Persona has been created. + Personal data that you add to your Persona will only be shared with dApps with your permission. + Some dApps may request personal information, like name or email address, that can be added to your Persona. Add some data now if you like. + This will be shared with dApps you login to + Create a Persona + A Persona is an identity that you own and control. You can have as many as you like. + Personas are used to login to dApps on Radix. dApps may request access to personal information associated with your Persona, like your name or email address. + Learn about Personas + Continue + What would you like to call your Persona? + e.g. My Main Persona + Continue + Required field + Save and Continue + Login Request + %s is requesting that you login with a Persona. + New Login Request + %s is requesting that you login for the **first time** with a Persona. + Choose a Persona + Your last login was on %s + Continue + Account Permission + %d or more accounts + Any number of accounts + %d accounts + 1 account + Continue + **%s** is requesting permission to *always* be able to view Account information when you login with this Persona. + You can update this permission in wallet settings for this dApp at any time. + Create a New Account + You are now connected to %s. You can change your preferences for this dApp in wallet settings at any time. + dApp Connection Successful + DApp error + Continue + Account Request + **%s** is making a one-time request for at least %d accounts. + **%s** is making a one-time request for any number of accounts. + **%s** is making a one-time request for at least 1 account. + **%s** is making a one-time request for %d accounts. + **%s** is making a one-time request for 1 account. + Account Permission + Choose at least %d accounts you wish to use with **%s**. + Choose any accounts you wish to use with **%s**. + Choose at least 1 account you wish to use with **%s**. + Choose %d accounts you wish to use with **%s**. + Choose 1 account you wish to use with **%s**. + One-Time Data Request + Choose the data to provide + **%s** is requesting that you provide some pieces of personal data **just one time** + Continue + Personal Data Permission + **%s** is requesting permission to **always** be able to view the following personal data when you login with this Persona. + You can update this permission in your Settings at any time. + Continue + Invalid content + Incompatible connector extension + Network mismatch + Invalid value of `numberOfAccountsInvalid`: must not be `exactly(0)` nor can `quantity` be negative + %s (CE: %s, wallet: %s) + \'%s\' is not valid origin. + \'%s\' is not valid account address. + Invalid data in request + Please update Radix Wallet + Please update Radix Connector browser extension + Invalid origin + Invalid dApp Definition Address + Radix Connect connection error + Invalid Request. + Could not validate the dApp. + Request received from dApp is invalid. + dApp specified an invalid Persona. + dApp made a request intended for network %s, but you are currently connected to %s. + Failed to send request response to dApp. + Success + Request from %s complete + Danger! Bad dApp configuration, or you\'re being spoofed! + Loading… + Unknown dApp + Radix Wallet + Edit + Required information: + Review Your Transaction + Proposed by %s + Review Your Transfer + Approve + Customize Guarantees + Estimated + Account + Guaranteed + Raw Transaction + Unknown + Unnamed dApp + Worth + To be claimed + Unknown pool + %d Unknown Components + %d Pool Components + Pool Units + Slide to Sign + %s XRD + Not enough XRD for transaction fee + Fee payer account required + Withdrawing From + Depositing To + Sending to + Message + Presenting + Using dApps + Third-party deposit setting + Third-party deposit exceptions + Staking to Validators + Requesting unstake from validators + Claim from validators + Contributing to pools + Redeeming from pools + Transaction Fee + The network is currently congested. Add a tip to speed up your transfer. + Customize + Customize Guarantees + Protect yourself by setting guaranteed minimums for estimated deposits + Apply + How do guarantees work? + Set guaranteed minimum %% + Preparing Transaction + Preparing transaction for signing + Submitting + Submitted but not confirmed + Rejected + Failed + Successfully committed + Transaction ID + Status + Submitting Transaction + Review New Deposit Settings + Third-party deposit setting + Allow third parties to deposit **any asset** to this account. + Allow third parties to deposit **only assets this account has already held**. + **Disallow** all deposits from third parties without your consent. + Allow + Disallow + Remove Exception + Add Depositor + Remove Depositor + A proposed transaction was rejected because it contains one or more reserved instructions. + Could Not Complete + The required seed phrase is missing. Please return to the account and begin the recovery process. + Warning + This is a complex transaction that cannot be summarized - the raw transaction manifest will be shown. Do not submit unless you understand the contents. + Completing Transaction… + Transaction ID: + Transaction Success + Transaction Failed + Transaction Rejected + Transaction Error + Your transaction was successful + Your transaction was processed, but had a problem that caused it to fail permanently + This transaction was rejected and is unlikely to be processed, but could potentially be processed within the next %s minutes. It is likely that the dApp you are using proposed a transaction that includes an action that is not currently valid. + Your transaction was improperly constructed and cannot be processed + Something Went Wrong + Transaction was rejected as invalid by the Radix Network. + Stop waiting for transaction result? The transaction will not be canceled. + Dismiss + This transaction requires to be completed + A guarantee on transaction results was not met. Consider reducing your preferred guarantee %% + Copy Address + Copied to Clipboard + Copy NFT ID + Copy Transaction ID + There is no web browser installed in this device + View on Radix Dashboard + Show Address QR Code + Verify Address with Ledger + Address verified + Verify address request failed + QR code for an account + Could not create QR code + Authenticate to continue + Authenticate to create new %s with this phone. + Authenticate to sign proof with this phone. + Authenticate to sign transaction with this phone. + Display seed phrase. + Create Auth signing key. + Check if seed phrase already exists. + Checking accounts. + Update account metadata. + Unsecured Device + Your device currently has no device access security set, such as biometrics or a PIN. The Radix Wallet requires this to be set for your security. + Open Settings + Quit + Account + Cancel + Continue + Connected to a test network, not Radix main network. + An Error Occurred + None + OK + Done + Copy + Persona + History + Pool + Retry + Remove + Something Went Wrong + Settings + Confirm + Save + Invalid + Public + Choose + Max + Optional + Show More + Show Less + Component + Unauthorized + Gateway access blocked due to exceeding rate limit. Please wait a few minutes to retry. + Bad HTTP response status code %d + Dismiss + Invalid Persona specified by dApp + Invalid request + Failed to import Radix Wallet backup: %s + Failed to import Radix Wallet backup, error: %s, version: %s + Failed to commit transaction + Failed to convert transaction manifest + Failed to get epoch + Failed to build transaction header + Failed to convert transaction manifest + You don\'t have access to some accounts or personas required to authorise this transaction + Wrong network + No funds to approve transaction + Failed to poll transaction status + Failed to prepare transaction + Transaction rejected + Unknown error + Failed to find ledger + Failed to add Transaction Fee, try a different amount of fee payer. + Failed to add Guarantee, try a different percentage, or try skip adding a guarantee. + A proposed transaction could not be processed. + Your current Ledger settings only allow signing of simple token transfers. Please either enable \"verbose mode\" (to see full transaction manifests) or \"blind signing mode\" (to enable signing of complex transaction manifest hashes) on your Ledger app device. + Failed to convert transaction manifest + Failed to submit transaction + Persona label too long + Account label too long + Account label required + One of the receiving accounts does not allow Third-Party deposits + Email Support + Please email support to automatically provide debugging info, and get assistance. Code: %s - Import Legacy Olympia Accounts - Scan the QR code shown in the Export section of the Radix Desktop Wallet for Olympia. - Scanned: %d/%d - Import Accounts - The following accounts will be imported into this Radix Wallet. - Legacy Account - Ledger (Legacy) - Unnamed - Olympia Address (Obsolete) - New Address - Import %d accounts - Import 1 account - Verify With Your Seed Phrase - To complete importing your accounts, please view your seed phrase in the Radix Desktop Wallet and enter the words here. - This will give this Radix Wallet control of your accounts. Never give your seed phrase to anyone for any reason. - Warning - Do not throw away this seed phrase! You will still need it if you need to recover access to your Olympia accounts in the future. - I Understand - Congratulations - You\'ve now imported these Accounts: - You\'ve now imported this Account: - Your Accounts live on the Radix Network and you can access them anytime in your Wallet. - Continue to Account List - Already imported - BIP39 passphrase - Import - Invalid Mnemonic - Invalid QR code - No mnemonic found for accounts - No new accounts were found on this Ledger device - Passphrase - Seed phrase - I\'m a New Radix Wallet User - Restore Wallet from Backup - Welcome to the Radix Wallet - Your direct connection to the Radix Network - A World of Possibilities - Let\'s get started - Your phone is your login - Connect and transact securely with a world of web3 dApps, tokens, NFTs, and much more - User Terms - To proceed, you must accept the user terms below. - Accept - Back up your Wallet Settings - Connect to Google Drive to automatically backup your Radix wallet settings. - Skip - Back up to Google Drive - Restore Wallet from Backup - Log in to Google Drive to restore your Radix wallet from Backup. - Skip - Log in to Google Drive - Tap to unlock - Passcode not set up - This app requires your phone to have a passcode set up - Delete Wallet Data - For this Preview wallet version, you must delete your wallet data to continue. - Wallet Data is Incompatible - Warning - Passcode is not set up. Please update settings. - Claim This Wallet? - Claim Existing Wallet - Clear Wallet on This Phone - Ask Later (no changes) - This wallet is currently configured with a set of Accounts and Personas in use by a different phone.\n\nTo make changes to this wallet, you must claim it for use on this phone instead, removing access by the other phone.\n\nOr you can clear this wallet from this phone and start fresh. - It appears that your device might be rooted. To ensure the security of your Accounts and assets, using the Radix Wallet on rooted devices is not recommended. Please confirm if you wish to continue anyway at your own risk. - Possible jailbreak detected - It appears that your device might be jailbroken. To ensure the security of your Accounts and assets, using the Radix Wallet on jailbroken devices is not recommended. Please confirm if you wish to continue anyway at your own risk. - I Understand the Risk - Approve Transaction - Incoming Transaction - Approve Transaction - Submitting transaction… - Import Seed Phrase - Backup Seed Phrase - Word %d - Passphrase - Passphrase - Optional BIP39 Passphrase. This is not your wallet password, and the Radix Desktop Wallet did not use a BIP39 passphrase. This is only to support import from other wallets that may have used one. - Number of Seed Phrase Words - Incorrect seed phrase - Success - Advanced Mode - Regular Mode - Import - Imported Seed Phrase - Confirm Seed Phrase Saved - Are you sure you have securely written down this seed phrase? You will need it to recover access if you lose your phone. - Yes, I have written it down - No, not yet - Failed to validate all accounts against mnemonic - Wrong mnemmonic - Tell a story - Hitchcock\'s The Birds mixed with Office space - Without revealing the words, what comes to mind when reading this seed phrase? - Backup location? - In that book my mother used to read to me at my best childhoods summer vacation place - Without revealing location, vague hint on where this mnemonic is backed up, if anywhere. - Save with description - Save without description - Enter this Account\'s seed phrase - Please write down seed phrase to ensure Account control - Change seed phrase length - %d word seed phrase - Recover Mnemonic - Can\'t displays image of vector type - Can\'t load image - Message - Add a message - Scan a QR code of a Radix Account address from another wallet or an exchange. - Continue - Account - From - To - Add Transfer - Transfer - Add Message - Invalid address - Account already added - Choose Receiving Account - Enter or scan an Account address - Or: Choose one of your own Accounts - Scan Account QR Code - Enter Radix Account address - Choose Asset(s) to Send - Select Assets - Choose 1 Asset - Choose %d Assets - Total exceeds your current balance - Balance: %s - Choose Account - Add Assets - Address is not valid on current network - Resource already added - Total amount exceeds your current balance - Sending All XRD - Sending the full amount of XRD in this account will require you to pay the transaction fee from a different account. Or, the wallet can reduce the amount transferred so the fee can be paid from this account. Choose the amount to transfer: - %s (send all XRD) - %s (save 1 XRD for fee) - You will be asked for an extra signature - Verify With Ledger Device - You are attempting to import one or more Olympia accounts that must be verified with a Ledger hardware wallet device. - Already verified Ledger devices: - Continue - Accounts remaining to verify: %d - Connect your next Ledger device, launch the Radix Babylon app on it, and tap Continue here. - Link a Connector - To use a Ledger hardware wallet device, it must be connected to a computer running the Radix Connector browser extension. - Continue - Choose Ledger - Choose Ledger Device - Ledger Devices - What is a Ledger Factor Source - Add Ledger Device - Added - Last Used - Continue - No Ledger devices currently added to your Radix Wallet - Choose a Ledger device to use - Choose an existing Ledger or add a new one - Here are all the Ledger devices you have added. - Could not find Ledger devices - Transaction could not be signed. To sign complex transactions, please enable either \"blind signing\" or \"verbose mode\" in the Radix app on your Ledger device. - Could Not Sign - Address verified - Verify address: Mismatched addresses - Verify address: Returned bad response - Verify address: Request failed - Use Caution - Reveal Seed Phrase - A seed phrase provides full control of its Accounts. Do not view in a public area. Write down the seed phrase words securely. Screenshots are disabled. - Seed Phrases - Connected to Personas and %d Account - Connected to Personas and to %d Accounts - Connected to %d Account - Connected to %d Accounts - You are responsible for the security of your Seed Phrase - Write Down this Seed Phrase - Begin seed phrase entry - Confirm Your Seed Phrase - Confirm you have written down the seed phrase by entering the missing words below. - Reveal Seed Phrase - For your safety, make sure no one is looking at your screen. Taking a screen shot has been disabled. - Word %d - Passphrase - Use Caution - Are you sure you have written down your seed phrase? - I have written down this seed phrase - Seed Phrases - A Seed Phrase provides full access to your accounts and funds. When viewing, ensure you’re in a safe environment and no one is looking at your screen. - You are responsible for the security of your Seed Phrase - Please write down your Seed Phrase - Hidden Accounts only - Seed Phrase Entry Required - Reveal Seed Phrase - Not connected to any Accounts - Connected to 1 Account - Connected to %d Accounts - Seed Phrase - Not yet connected to any Accounts - Currently connected to 1 Account - Currently connected to %d Accounts - App Settings - Customize your Radix Wallet - Linked Connectors - Network Gateways - Account & Persona Hiding - Manage hiding - Accounts and Personas you have created can be hidden in your Radix Wallet, acting as if “deleted”. - %d Persona currently hidden - %d Personas currently hidden - %d Account currently hidden - %d Accounts currently hidden - Unhide Accounts & Personas - Unhide All - Are you sure you wish to unhide all Accounts and Personas? This cannot be undone. - Developer Mode - Warning: Disables website validity checks - Crash Reporting - I\'m aware Radix Wallet will send crash reports together with device state from the moment of crash. - Ledger Already Added - You have already added this Ledger as: %s - Add Ledger Device - Let’s set up a Ledger hardware wallet device. You will be able to use it to create new Ledger-secured Accounts, or import Ledger-secured Accounts from the Radix Olympia Desktop Wallet. - Connect your Ledger to a computer running a linked Radix Connector browser extension, and make sure the Radix Babylon app is running on the Ledger device. - Continue - Name Your Ledger - What would you like to call this Ledger device? - This will be displayed when you’re prompted to sign with this Ledger device. - Detected type: %s - Green Ledger Nano S+ - Save and Continue - Backing up your wallet ensures that you can restore access to your Accounts, Personas, and wallet settings on a new phone by re-entering your seed phrase(s).\n\n**For security, backups do not contain any seed phrases or private keys. You must write them down separately.** - Automatic Backups (recommended) - Manual Backups - A manually exported wallet backup file may also be used for recovery, along with your seed phrase(s).\n\nOnly the **current configuration** of your wallet is backed up with each manual export. - Export Wallet Backup File - Encrypt this backup with a password? - Exported wallet backup file - Yes - No - Encrypt Wallet Backup File - Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. - Decrypt Wallet Backup File - Enter the password you chose when you originally encrypted this Wallet Backup file. - Enter password - Confirm password - Passwords do not match - Encryption password - Decryption password - Incorrect password - OK - Failed to encrypt using provided password. - Failed to decrypt using provided password. - Delete Wallet - WARNING. This will clear all contents of your Wallet. If you have no backup, you will lose access to your Accounts and Personas permanently. - Delete Wallet - Reset Wallet? - Reset Wallet - Reset and Delete iCloud Backup - WARNING. This will clear all contents of your Wallet. If you have no backup, you will lose access to your Accounts and Personas permanently. - Back up is turned off - Last Backed up: %s - Not backed up yet - Open System Backup Settings - Backup Wallet Data to Cloud - Warning: If disabled you might lose access to your Accounts and Personas. - You may delete your wallet. This will clear the Radix Wallet app, clears its contents, and delete any cloud backup.\n\n**Access to any Accounts or Personas will be permanently lost unless you have a manual backup file.** - Delete Wallet - Enabling iCloud sync - iCloud sync is now enabled, but it might take up to an hour before your wallet data is uploaded to iCloud. - Disabling iCloud sync will delete the iCloud backup data, are you sure you want to disable iCloud sync? - Enable Backup to iCloud - Disable Backup to iCloud - Automatic continuous backups - Sync Wallet Data to iCloud - Warning: If disabled you might lose access to your Accounts and Personas. - Wallet Data Backup - Import From Backup - Available backups: - Use iCloud Backup Data - This Device - Backup created by: %s - Creation date: %s - Last used on device: %s - Last modified date: %s - Number of networks: %d - Number of Accounts: %d - Number of Personas: %d - Incompatible Wallet data - You may delete your wallet. This will clear the Radix Wallet app, clears its contents, and delete any iCloud backup.\n\n**Access to any Accounts or Personas will be permanently lost unless you have a manual backup file.** - Delete Wallet and iCloud Backup - Unable to find wallet backup in iCloud. - Encrypt Wallet Backup File - Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. - Enter password - Confirm password - Passwords do not match - Restore Wallet From Backup - Select a backup to restore your Radix Wallet. You will be asked to enter your seed phrase(s) to recover control of your Accounts and Personas. - Choose a backup - Choose a backup on iCloud - No wallet backups available on current iCloud account - Not logged in to iCloud - Network unavailable - Could not load backups - No wallet backups available - **Backup from:** %s - **Last modified:** %s - **Number of accounts:** %d - **Number of personas:** %d - Import from Backup File Instead - Incompatible Wallet data - The password is wrong - Backup not available? - Other Restore Options - Main Seed Phrase Required - No Main Seed Phrase? - Seed Phrase Required - Your **Personas** and the following **Accounts** are controlled by your main seed phrase. To recover control, you must re-enter it. - The following **Accounts** are controlled by a seed phrase. To recover control, you must re-enter it. - The Radix Wallet always uses a single main “Babylon” seed phrase to generate new Personas and new Accounts (when not using a Ledger device).\n\nIf you do not have access to your previous main seed phrase, you can skip entering it for now. **The Radix Wallet will create a new one, which will be used for new Personas and Accounts.**\n\nYour old Accounts and Personas will still be listed, but you will have to enter their original seed phrase to use them. Alternatively, you can hide them if you no longer are interested in using them. - Skip This Seed Phrase For Now - I Don’t Have the Main Seed Phrase - Enter This Seed Phrase - Skip Main Seed Phrase Entry - Hidden accounts only. - Phone - Ledger - Seed phrase - Third-party - Security Questions - Customize Fees - Estimated Transaction Fees - Transaction Fee - (maximum to lock) - Advanced Customize Fees - Choose what account to pay the transaction fee from, or add a \"tip\" to speed up your transaction if necessary. - Fully customize fee payment for this transaction. Not recomended unless you are a developer or advanced user. - Pay fee from - Change - None required - None due - No account selected - Select Fee Payer - Select an account to pay %s XRD transaction fee - Select Account - View Normal Mode - View Advanced Mode - Network Fee - Network Execution - Network Finalization - Effective Tip - Network Storage - Padding - Royalties - Royalty fee - Paid by dApps - Adjust Fee Padding Amount (XRD) - Adjust Tip to Lock - (%% of Execution + Finalization Fees) - Please select a fee payer for the transaction fee - Not enough XRD for transaction fee - Scanning in progress - Scanning for Accounts that have been included in at least one transaction, using: - **Babylon Seed Phrase** - **Olympia Seed Phrase** - **Ledger hardware wallet device** - Signing Factor - Unnamed - Scanning network - Scan Complete - The first **%d** potential Accounts from this signing factor were scanned. The following Accounts had at least one transaction: - No new accounts found - Tap here to scan the next %d - Continue - Add Inactive Accounts? - These Accounts were never used, but you **may** have created them. Select any addresses that you wish to keep: - Continue - Account Recovery Scan - The Radix Wallet can scan for previously used accounts using a bare seed phrase or Ledger hardware wallet device - Babylon Accounts - Scan for Accounts originally created on the **Babylon** network. - Olympia Accounts - Scan for Accounts originally created on the **Olympia** network.\n\n(If you have Olympia Accounts in the Radix Olympia Desktop Wallet, consider using **Import from a Legacy Wallet** instead. - Note: You will still use the new **Radix Babylon** app on your Ledger device, not the old Radix Ledger app. - Use Seed Phrase - Use Ledger Hardware Wallet - Choose Seed Phrase - Choose the \"Legacy\" Olympia seed phrase for use for derivation: - Choose the Babylon seed phrase for use for derivation: - Add Babylon Seed Phrase - Add Olympia Seed Phrase - Enter Legacy Seed Phrase - Enter Seed Phrase - Continue - Note: You must still use the new *Radix Babylon* app on your Ledger device, not the old Radix Ledger app. - Seed Phrases - Ledger Hardware Wallets - Default Deposit Guarantees - Set your default guaranteed minimum for estimated deposits - Set the guaranteed minimum deposit to be applied whenever a deposit in a transaction can only be estimated.\n\nYou can always change the guarantee from this default in each transaction. - Account Recovery Scan - Using seed phrase or Ledger device - Backups - Import from a Legacy Wallet - Enter Seed Phrase - Enter Main Seed Phrase - For your safety, make sure no one is looking at your screen. Never give your seed phrase to anyone for any reason. - Enter Main Seed Phrase - Enter Babylon Seed Phrase - Enter Olympia Seed Phrase - Recover Control Without Backup - If you have no wallet backup in the cloud, or as an exported backup file, you still have other restore options. - I have my main “Babylon” 24-word seed phrase. - Recover with Main Seed Phrase - I only want to restore Ledger hardware wallet Accounts. - Ledger-only Restore - I only have Accounts created on the Radix Olympia network. - Olympia-only Restore - No Main Seed Phrase? - Tap “I\'m a New Wallet User”. After completing wallet creation, in Settings you can perform an “account recovery scan” using your Ledger device. - Tap “I\'m a New Wallet User”. After completing wallet creation, in Settings you can perform an “account recovery scan” using your Olympia seed phrase. - Cancel - Continue - Recover Control Without Backup - **If you have no wallet backup in the cloud or as an exported backup file**, you can still restore Account access only using your main “Babylon” seed phrase. You cannot recover your Account names or other wallet settings this way.\n\nYou will be asked to enter your main seed phrase. This is a set of **24 words** that the Radix Wallet mobile app showed you to write down and save securely. - Continue - Recovery Complete - Accounts discovered in the scan have been added to your wallet.\n\nIf you have any “Legacy” Accounts (created on the Olympia network) to import - or any Accounts using a Ledger hardware wallet device - please continue and then use the **Account Recovery Scan** option in your Radix Wallet settings under **Account Security**. - Continue - History - Settings - Withdrawn - Deposited - Updated Account Deposit Settings - No deposits or withdrawals from this account in this transaction. - This transaction cannot be summarized. Only the raw transaction manifest may be viewed. - Failed Transaction - You have no Transactions. - General - Transfer - Stake - Request Unstake - Claim Stake - Contribute - Redeem - Deposit Settings - Other - Filter - Clear All - Show Results - Type of Asset - Show All Tokens - Show Less Tokens - Show All NFTs - Show Less NFTs - Tokens - Deposits - Withdrawals - NFTs - Type of Transaction - Today - Yesterday - How\'s it Going? - How likely are you to recommend Radix and the Radix Wallet to your friends or colleagues? - 0 - Not likely - 10 - Very likely - What’s the main reason for your score? - Let us know... - Submit Feedback - Thanks! - Signature Request - Creating Account - Creating Persona - Deriving Accounts - Proving Ownership - Encrypting Message - Creating Key - Authenticate to your phone to sign. - Authenticate to your phone to complete using your phone\'s signing key. - Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. + Import Legacy Olympia Accounts + Scan the QR code shown in the Export section of the Radix Desktop Wallet for Olympia. + Scanned: %d/%d + Import Accounts + The following accounts will be imported into this Radix Wallet. + Legacy Account + Ledger (Legacy) + Unnamed + Olympia Address (Obsolete) + New Address + Import %d accounts + Import 1 account + Verify With Your Seed Phrase + To complete importing your accounts, please view your seed phrase in the Radix Desktop Wallet and enter the words here. + This will give this Radix Wallet control of your accounts. Never give your seed phrase to anyone for any reason. + Warning + Do not throw away this seed phrase! You will still need it if you need to recover access to your Olympia accounts in the future. + I Understand + Congratulations + You\'ve now imported these Accounts: + You\'ve now imported this Account: + Your Accounts live on the Radix Network and you can access them anytime in your Wallet. + Continue to Account List + Already imported + BIP39 passphrase + Import + Invalid Mnemonic + Invalid QR code + No mnemonic found for accounts + No new accounts were found on this Ledger device + Passphrase + Seed phrase + I\'m a New Radix Wallet User + Restore Wallet from Backup + Welcome to the Radix Wallet + Your direct connection to the Radix Network + A World of Possibilities + Let\'s get started + Your phone is your login + Connect and transact securely with a world of web3 dApps, tokens, NFTs, and much more + User Terms + To proceed, you must accept the user terms below. + Accept + Back up your Wallet Settings + Connect to Google Drive to automatically backup your Radix wallet settings. + Skip + Back up to Google Drive + Restore Wallet from Backup + Log in to Google Drive to restore your Radix wallet from Backup. + Skip + Log in to Google Drive + Tap to unlock + Passcode not set up + This app requires your phone to have a passcode set up + Delete Wallet Data + For this Preview wallet version, you must delete your wallet data to continue. + Wallet Data is Incompatible + Warning + Passcode is not set up. Please update settings. + Claim This Wallet? + Claim Existing Wallet + Clear Wallet on This Phone + Ask Later (no changes) + This wallet is currently configured with a set of Accounts and Personas in use by a different phone.\n\nTo make changes to this wallet, you must claim it for use on this phone instead, removing access by the other phone.\n\nOr you can clear this wallet from this phone and start fresh. + It appears that your device might be rooted. To ensure the security of your Accounts and assets, using the Radix Wallet on rooted devices is not recommended. Please confirm if you wish to continue anyway at your own risk. + Possible jailbreak detected + It appears that your device might be jailbroken. To ensure the security of your Accounts and assets, using the Radix Wallet on jailbroken devices is not recommended. Please confirm if you wish to continue anyway at your own risk. + I Understand the Risk + Approve Transaction + Incoming Transaction + Approve Transaction + Submitting transaction… + Import Seed Phrase + Backup Seed Phrase + Word %d + Passphrase + Passphrase + Optional BIP39 Passphrase. This is not your wallet password, and the Radix Desktop Wallet did not use a BIP39 passphrase. This is only to support import from other wallets that may have used one. + Number of Seed Phrase Words + Incorrect seed phrase + Success + Advanced Mode + Regular Mode + Import + Imported Seed Phrase + Confirm Seed Phrase Saved + Are you sure you have securely written down this seed phrase? You will need it to recover access if you lose your phone. + Yes, I have written it down + No, not yet + Failed to validate all accounts against mnemonic + Wrong mnemmonic + Tell a story + Hitchcock\'s The Birds mixed with Office space + Without revealing the words, what comes to mind when reading this seed phrase? + Backup location? + In that book my mother used to read to me at my best childhoods summer vacation place + Without revealing location, vague hint on where this mnemonic is backed up, if anywhere. + Save with description + Save without description + Enter this Account\'s seed phrase + Please write down seed phrase to ensure Account control + Change seed phrase length + %d word seed phrase + Recover Mnemonic + Can\'t displays image of vector type + Can\'t load image + Message + Add a message + Scan a QR code of a Radix Account address from another wallet or an exchange. + Continue + Account + From + To + Add Transfer + Transfer + Add Message + Invalid address + Account already added + Choose Receiving Account + Enter or scan an Account address + Or: Choose one of your own Accounts + Scan Account QR Code + Enter Radix Account address + Choose Asset(s) to Send + Select Assets + Choose 1 Asset + Choose %d Assets + Total exceeds your current balance + Balance: %s + Choose Account + Add Assets + Address is not valid on current network + Resource already added + Total amount exceeds your current balance + Sending All XRD + Sending the full amount of XRD in this account will require you to pay the transaction fee from a different account. Or, the wallet can reduce the amount transferred so the fee can be paid from this account. Choose the amount to transfer: + %s (send all XRD) + %s (save 1 XRD for fee) + You will be asked for an extra signature + Verify With Ledger Device + You are attempting to import one or more Olympia accounts that must be verified with a Ledger hardware wallet device. + Already verified Ledger devices: + Continue + Accounts remaining to verify: %d + Connect your next Ledger device, launch the Radix Babylon app on it, and tap Continue here. + Link a Connector + To use a Ledger hardware wallet device, it must be connected to a computer running the Radix Connector browser extension. + Continue + Choose Ledger + Choose Ledger Device + Ledger Devices + What is a Ledger Factor Source + Add Ledger Device + Added + Last Used + Continue + No Ledger devices currently added to your Radix Wallet + Choose a Ledger device to use + Choose an existing Ledger or add a new one + Here are all the Ledger devices you have added. + Could not find Ledger devices + Transaction could not be signed. To sign complex transactions, please enable either \"blind signing\" or \"verbose mode\" in the Radix app on your Ledger device. + Could Not Sign + Address verified + Verify address: Mismatched addresses + Verify address: Returned bad response + Verify address: Request failed + Use Caution + Reveal Seed Phrase + A seed phrase provides full control of its Accounts. Do not view in a public area. Write down the seed phrase words securely. Screenshots are disabled. + Seed Phrases + Connected to Personas and %d Account + Connected to Personas and to %d Accounts + Connected to %d Account + Connected to %d Accounts + You are responsible for the security of your Seed Phrase + Write Down this Seed Phrase + Begin seed phrase entry + Confirm Your Seed Phrase + Confirm you have written down the seed phrase by entering the missing words below. + Reveal Seed Phrase + For your safety, make sure no one is looking at your screen. Taking a screen shot has been disabled. + Word %d + Passphrase + Use Caution + Are you sure you have written down your seed phrase? + I have written down this seed phrase + Seed Phrases + A Seed Phrase provides full access to your accounts and funds. When viewing, ensure you’re in a safe environment and no one is looking at your screen. + You are responsible for the security of your Seed Phrase + Please write down your Seed Phrase + Hidden Accounts only + Seed Phrase Entry Required + Reveal Seed Phrase + Not connected to any Accounts + Connected to 1 Account + Connected to %d Accounts + Seed Phrase + Not yet connected to any Accounts + Currently connected to 1 Account + Currently connected to %d Accounts + App Settings + Customize your Radix Wallet + Linked Connectors + Network Gateways + Account & Persona Hiding + Manage hiding + Accounts and Personas you have created can be hidden in your Radix Wallet, acting as if “deleted”. + %d Persona currently hidden + %d Personas currently hidden + %d Account currently hidden + %d Accounts currently hidden + Unhide Accounts & Personas + Unhide All + Are you sure you wish to unhide all Accounts and Personas? This cannot be undone. + Developer Mode + Warning: Disables website validity checks + Crash Reporting + I\'m aware Radix Wallet will send crash reports together with device state from the moment of crash. + Ledger Already Added + You have already added this Ledger as: %s + Add Ledger Device + Let’s set up a Ledger hardware wallet device. You will be able to use it to create new Ledger-secured Accounts, or import Ledger-secured Accounts from the Radix Olympia Desktop Wallet. + Connect your Ledger to a computer running a linked Radix Connector browser extension, and make sure the Radix Babylon app is running on the Ledger device. + Continue + Name Your Ledger + What would you like to call this Ledger device? + This will be displayed when you’re prompted to sign with this Ledger device. + Detected type: %s + Green Ledger Nano S+ + Save and Continue + Backing up your wallet ensures that you can restore access to your Accounts, Personas, and wallet settings on a new phone by re-entering your seed phrase(s).\n\n**For security, backups do not contain any seed phrases or private keys. You must write them down separately.** + Automatic Backups (recommended) + Manual Backups + A manually exported wallet backup file may also be used for recovery, along with your seed phrase(s).\n\nOnly the **current configuration** of your wallet is backed up with each manual export. + Export Wallet Backup File + Encrypt this backup with a password? + Exported wallet backup file + Yes + No + Encrypt Wallet Backup File + Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. + Decrypt Wallet Backup File + Enter the password you chose when you originally encrypted this Wallet Backup file. + Enter password + Confirm password + Passwords do not match + Encryption password + Decryption password + Incorrect password + OK + Failed to encrypt using provided password. + Failed to decrypt using provided password. + Delete Wallet + WARNING. This will clear all contents of your Wallet. If you have no backup, you will lose access to your Accounts and Personas permanently. + Delete Wallet + Reset Wallet? + Reset Wallet + Reset and Delete iCloud Backup + WARNING. This will clear all contents of your Wallet. If you have no backup, you will lose access to your Accounts and Personas permanently. + Back up is turned off + Last Backed up: %s + Not backed up yet + Open System Backup Settings + Backup Wallet Data to Cloud + Warning: If disabled you might lose access to your Accounts and Personas. + You may delete your wallet. This will clear the Radix Wallet app, clears its contents, and delete any cloud backup.\n\n**Access to any Accounts or Personas will be permanently lost unless you have a manual backup file.** + Delete Wallet + Enabling iCloud sync + iCloud sync is now enabled, but it might take up to an hour before your wallet data is uploaded to iCloud. + Disabling iCloud sync will delete the iCloud backup data, are you sure you want to disable iCloud sync? + Enable Backup to iCloud + Disable Backup to iCloud + Automatic continuous backups + Sync Wallet Data to iCloud + Warning: If disabled you might lose access to your Accounts and Personas. + Wallet Data Backup + Import From Backup + Available backups: + Use iCloud Backup Data + This Device + Backup created by: %s + Creation date: %s + Last used on device: %s + Last modified date: %s + Number of networks: %d + Number of Accounts: %d + Number of Personas: %d + Incompatible Wallet data + You may delete your wallet. This will clear the Radix Wallet app, clears its contents, and delete any iCloud backup.\n\n**Access to any Accounts or Personas will be permanently lost unless you have a manual backup file.** + Delete Wallet and iCloud Backup + Unable to find wallet backup in iCloud. + Encrypt Wallet Backup File + Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. + Enter password + Confirm password + Passwords do not match + Restore Wallet From Backup + Select a backup to restore your Radix Wallet. You will be asked to enter your seed phrase(s) to recover control of your Accounts and Personas. + Choose a backup + Choose a backup on iCloud + No wallet backups available on current iCloud account + Not logged in to iCloud + Network unavailable + Could not load backups + No wallet backups available + **Backup from:** %s + **Last modified:** %s + **Number of accounts:** %d + **Number of personas:** %d + Import from Backup File Instead + Incompatible Wallet data + The password is wrong + Backup not available? + Other Restore Options + Main Seed Phrase Required + No Main Seed Phrase? + Seed Phrase Required + Your **Personas** and the following **Accounts** are controlled by your main seed phrase. To recover control, you must re-enter it. + The following **Accounts** are controlled by a seed phrase. To recover control, you must re-enter it. + The Radix Wallet always uses a single main “Babylon” seed phrase to generate new Personas and new Accounts (when not using a Ledger device).\n\nIf you do not have access to your previous main seed phrase, you can skip entering it for now. **The Radix Wallet will create a new one, which will be used for new Personas and Accounts.**\n\nYour old Accounts and Personas will still be listed, but you will have to enter their original seed phrase to use them. Alternatively, you can hide them if you no longer are interested in using them. + Skip This Seed Phrase For Now + I Don’t Have the Main Seed Phrase + Enter This Seed Phrase + Skip Main Seed Phrase Entry + Hidden accounts only. + Phone + Ledger + Seed phrase + Third-party + Security Questions + Customize Fees + Estimated Transaction Fees + Transaction Fee + (maximum to lock) + Advanced Customize Fees + Choose what account to pay the transaction fee from, or add a \"tip\" to speed up your transaction if necessary. + Fully customize fee payment for this transaction. Not recomended unless you are a developer or advanced user. + Pay fee from + Change + None required + None due + No account selected + Select Fee Payer + Select an account to pay %s XRD transaction fee + Select Account + View Normal Mode + View Advanced Mode + Network Fee + Network Execution + Network Finalization + Effective Tip + Network Storage + Padding + Royalties + Royalty fee + Paid by dApps + Adjust Fee Padding Amount (XRD) + Adjust Tip to Lock + (%% of Execution + Finalization Fees) + Please select a fee payer for the transaction fee + Not enough XRD for transaction fee + Scanning in progress + Scanning for Accounts that have been included in at least one transaction, using: + **Babylon Seed Phrase** + **Olympia Seed Phrase** + **Ledger hardware wallet device** + Signing Factor + Unnamed + Scanning network + Scan Complete + The first **%d** potential Accounts from this signing factor were scanned. The following Accounts had at least one transaction: + No new accounts found + Tap here to scan the next %d + Continue + Add Inactive Accounts? + These Accounts were never used, but you **may** have created them. Select any addresses that you wish to keep: + Continue + Account Recovery Scan + The Radix Wallet can scan for previously used accounts using a bare seed phrase or Ledger hardware wallet device + Babylon Accounts + Scan for Accounts originally created on the **Babylon** network. + Olympia Accounts + Scan for Accounts originally created on the **Olympia** network.\n\n(If you have Olympia Accounts in the Radix Olympia Desktop Wallet, consider using **Import from a Legacy Wallet** instead. + Note: You will still use the new **Radix Babylon** app on your Ledger device, not the old Radix Ledger app. + Use Seed Phrase + Use Ledger Hardware Wallet + Choose Seed Phrase + Choose the \"Legacy\" Olympia seed phrase for use for derivation: + Choose the Babylon seed phrase for use for derivation: + Add Babylon Seed Phrase + Add Olympia Seed Phrase + Enter Legacy Seed Phrase + Enter Seed Phrase + Continue + Note: You must still use the new *Radix Babylon* app on your Ledger device, not the old Radix Ledger app. + Seed Phrases + Ledger Hardware Wallets + Default Deposit Guarantees + Set your default guaranteed minimum for estimated deposits + Set the guaranteed minimum deposit to be applied whenever a deposit in a transaction can only be estimated.\n\nYou can always change the guarantee from this default in each transaction. + Account Recovery Scan + Using seed phrase or Ledger device + Backups + Import from a Legacy Wallet + Enter Seed Phrase + Enter Main Seed Phrase + For your safety, make sure no one is looking at your screen. Never give your seed phrase to anyone for any reason. + Enter Main Seed Phrase + Enter Babylon Seed Phrase + Enter Olympia Seed Phrase + Recover Control Without Backup + If you have no wallet backup in the cloud, or as an exported backup file, you still have other restore options. + I have my main “Babylon” 24-word seed phrase. + Recover with Main Seed Phrase + I only want to restore Ledger hardware wallet Accounts. + Ledger-only Restore + I only have Accounts created on the Radix Olympia network. + Olympia-only Restore + No Main Seed Phrase? + Tap “I\'m a New Wallet User”. After completing wallet creation, in Settings you can perform an “account recovery scan” using your Ledger device. + Tap “I\'m a New Wallet User”. After completing wallet creation, in Settings you can perform an “account recovery scan” using your Olympia seed phrase. + Cancel + Continue + Recover Control Without Backup + **If you have no wallet backup in the cloud or as an exported backup file**, you can still restore Account access only using your main “Babylon” seed phrase. You cannot recover your Account names or other wallet settings this way.\n\nYou will be asked to enter your main seed phrase. This is a set of **24 words** that the Radix Wallet mobile app showed you to write down and save securely. + Continue + Recovery Complete + Accounts discovered in the scan have been added to your wallet.\n\nIf you have any “Legacy” Accounts (created on the Olympia network) to import - or any Accounts using a Ledger hardware wallet device - please continue and then use the **Account Recovery Scan** option in your Radix Wallet settings under **Account Security**. + Continue + History + Settings + Withdrawn + Deposited + Updated Account Deposit Settings + No deposits or withdrawals from this account in this transaction. + This transaction cannot be summarized. Only the raw transaction manifest may be viewed. + Failed Transaction + You have no Transactions. + General + Transfer + Stake + Request Unstake + Claim Stake + Contribute + Redeem + Deposit Settings + Other + Filter + Clear All + Show Results + Type of Asset + Show All Tokens + Show Less Tokens + Show All NFTs + Show Less NFTs + Tokens + Deposits + Withdrawals + NFTs + Type of Transaction + Today + Yesterday + How\'s it Going? + How likely are you to recommend Radix and the Radix Wallet to your friends or colleagues? + 0 - Not likely + 10 - Very likely + What’s the main reason for your score? + Let us know... + Submit Feedback - Thanks! + Signature Request + Creating Account + Creating Persona + Deriving Accounts + Proving Ownership + Encrypting Message + Creating Key + Authenticate to your phone to sign. + Authenticate to your phone to complete using your phone\'s signing key. + Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. **Complete signing on the device.** - Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. + Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. **Derivation may take up to a minute.** - Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. - Security Center - Decentralized security settings that give you total control over your wallet’s protection. - Your wallet is recoverable - Security Factors - The keys you use to control your Accounts and Personas - Active - Configuration Backup - A backup of your Account, Personas and wallet settings - Backed up - Action required - Encrypt Wallet Backup File - Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. - Enter Password - Confirm Password - Passwords do not match - Continue - Security Factors - View and manage your security factors - Seed Phrases - Your seedphrases connected to your account - 1 Seed phrase - %d Seed phrases - Enter your seed phrase to recover Accounts - Ledger Hardware Wallets - Hardware wallet designed for holding crypto - 1 set - %d set - Configuration Backup - You need an up-to-date Configuration Backup to recover your Accounts and Personas if you lose access to them.\n\nYour Backup does not contain your keys or seed phrase. - Configuration Backup status - Without an updated Configuration Backup, you cannot recover your Accounts and Personas. - Automated iCloud Backups - Automated Google Drive Backups - Last backup: %s - Log in to Google Drive - Logged in as: - Disconnect - Out-of-date backup still present on iCloud - Delete - Accounts - Your list of Accounts and the Factors required to recover them - Personas - Your list of Personas and the Factors required to recover them. Also your Persona data. - Security Factors - The list of Security Factors you need to recover your Accounts and Personas. - Wallet settings - Your general settings, such as trusted dApps, linked Connectors and wallet display settings. - Wallet Control Has Been Transferred - The current wallet configuration is now controlled by another phone. - If this was done in error, you can reclaim control to this phone. You won’t be able to access it from your old phone after the transfer. - Or, you can clear the wallet configuration from this phone and start fresh. - Clear Wallet on This Phone - Transfer Control Back to This Phone - Backups on Google Drive Have Updated - The Radix Wallet has an all new and improved backup system.\n\nTo continue, log in with the Google Drive account you want to use for backups. - Login to Google Drive for Backups - Skip for Now - Manual backup - You can export your own Configuration Backup file and save it locally - You’ll need to export a new Backup file each time you make a change in your wallet. - Export Backup File - Last backup: %s - Wallet Settings - Security Center - Manage your wallet security settings - Personas - Manage Radix dApp login details - Please write down the seed phrase for your Personas - Approved dApps - Manage the Radix dApps you\'re connected to - Linked Connectors - Connect to desktop through the Radix Connector browser extension - Preferences - Deposits, hidden Accounts and Personas, and advanced preferences - Troubleshooting - Add your existing Accounts and contact support - App version: %s - Link your Wallet to a desktop browser - Scan the QR code in the Radix Wallet Connector extension - Link to Connector - Preferences - Default Deposit Guarantees - Set your guaranteed minimum for estimated deposits - Hidden Accounts and Personas - Manage hidden Accounts and Personas - Advanced Preferences - Network Gateways - Developer Mode - Warning: disables website validity checks - Troubleshooting - Account Recovery - Account Recovery Scan - Recover Accounts with a seed phrase or Ledger device - Import from a Legacy Wallet - Import Accounts from an Olympia wallet - Support and Community - Contact Support - Connect directly with the Radix support team - Discord - Connect to the official Radix Discord channel to join the community and ask for help. - Reset Account - Factory Reset - Restore your Radix wallet to its original state - Address QR Code - Full address - Copy - Enlarge - Share - View on Radix Dashboard - Verify Address on Ledger Device - Could not create QR code - Today - Yesterday - Tomorrow - %s ago - Just now - Factory Reset - A factory reset will restore your Radix wallet to its original settings. All of your data and preferences will be erased. - Security Center status - Your wallet is recoverable - Your wallet is not recoverable - Your wallet is currently unrecoverable. If you do a factory reset now, you will never be able to access your Accounts and Personas again. - Once you’ve completed a factory reset, you will not be able to access your Accounts and Personas unless you do a full recovery. - Reset Wallet - Confirm factory reset - Return wallet to factory settings? You cannot undo this. - You need to write down a seed phrase - %s and %s are not recoverable. - %s and %s (plus some hidden) are not recoverable. - View and write down your seed phrase so Accounts and Personas are recoverable. - View and write down seed phrase - View and write down seed phrase - Personas are not recoverable - You need to write down a seed phrase - Problem with Configuration Backup - Your wallet is not recoverable - Automated Configuration Backup has stopped working. Check internet and cloud settings. - Automated Configuration Backup not working. Check internet connection and cloud settings. - Personas are not recoverable - Problem with Configuration Backup - Your wallet is not recoverable - Your wallet is not recoverable - Configuration Backup is not up to date. Create backup now. - To secure your wallet, turn on automated backups or manually export backup file. - Personas are not recoverable - Your wallet is not recoverable - Configuration Backup not up to date - Your wallet is not recoverable - Accounts and Personas not recoverable. Create Configuration Backup now. - Configuration Backup not up to date. Turn on automated backups or manually export backup file. - Personas are not recoverable - Configuration Backup not up to date - Recovery required - Recovery required - Enter seed phrase to recover control. - Enter seed phrase to recover control - Enter seed phrase to recover control - Recovery required - Recovery required - 1 account - %d accounts - 1 persona - %d personas + Make sure the following **Ledger hardware wallet** is connected to a computer with a linked Radix Connector browser extension. + Security Center + Decentralized security settings that give you total control over your wallet’s protection. + Your wallet is recoverable + Security Factors + The keys you use to control your Accounts and Personas + Active + Configuration Backup + A backup of your Account, Personas and wallet settings + Backed up + Action required + Encrypt Wallet Backup File + Enter a password to encrypt this wallet backup file. You will be required to enter this password when recovering your Wallet from this file. + Enter Password + Confirm Password + Passwords do not match + Continue + Security Factors + View and manage your security factors + Seed Phrases + Your seedphrases connected to your account + 1 Seed phrase + %d Seed phrases + Enter your seed phrase to recover Accounts + Ledger Hardware Wallets + Hardware wallet designed for holding crypto + 1 set + %d set + Configuration Backup + You need an up-to-date Configuration Backup to recover your Accounts and Personas if you lose access to them.\n\nYour Backup does not contain your keys or seed phrase. + Configuration Backup status + Without an updated Configuration Backup, you cannot recover your Accounts and Personas. + Automated iCloud Backups + Automated Google Drive Backups + Last backup: %s + Log in to Google Drive + Logged in as: + Disconnect + Out-of-date backup still present on iCloud + Delete + Accounts + Your list of Accounts and the Factors required to recover them + Personas + Your list of Personas and the Factors required to recover them. Also your Persona data. + Security Factors + The list of Security Factors you need to recover your Accounts and Personas. + Wallet settings + Your general settings, such as trusted dApps, linked Connectors and wallet display settings. + Wallet Control Has Been Transferred + The current wallet configuration is now controlled by another phone. + If this was done in error, you can reclaim control to this phone. You won’t be able to access it from your old phone after the transfer. + Or, you can clear the wallet configuration from this phone and start fresh. + Clear Wallet on This Phone + Transfer Control Back to This Phone + Backups on Google Drive Have Updated + The Radix Wallet has an all new and improved backup system.\n\nTo continue, log in with the Google Drive account you want to use for backups. + Login to Google Drive for Backups + Skip for Now + Manual backup + You can export your own Configuration Backup file and save it locally + You’ll need to export a new Backup file each time you make a change in your wallet. + Export Backup File + Last backup: %s + Wallet Settings + Security Center + Manage your wallet security settings + Personas + Manage Radix dApp login details + Please write down the seed phrase for your Personas + Approved dApps + Manage the Radix dApps you\'re connected to + Linked Connectors + Connect to desktop through the Radix Connector browser extension + Preferences + Deposits, hidden Accounts and Personas, and advanced preferences + Troubleshooting + Add your existing Accounts and contact support + App version: %s + Link your Wallet to a desktop browser + Scan the QR code in the Radix Wallet Connector extension + Link to Connector + Preferences + Default Deposit Guarantees + Set your guaranteed minimum for estimated deposits + Hidden Accounts and Personas + Manage hidden Accounts and Personas + Advanced Preferences + Network Gateways + Developer Mode + Warning: disables website validity checks + Troubleshooting + Account Recovery + Account Recovery Scan + Recover Accounts with a seed phrase or Ledger device + Import from a Legacy Wallet + Import Accounts from an Olympia wallet + Support and Community + Contact Support + Connect directly with the Radix support team + Discord + Connect to the official Radix Discord channel to join the community and ask for help. + Reset Account + Factory Reset + Restore your Radix wallet to its original state + Address QR Code + Full address + Copy + Enlarge + Share + View on Radix Dashboard + Verify Address on Ledger Device + Could not create QR code + Today + Yesterday + Tomorrow + %s ago + Just now + Factory Reset + A factory reset will restore your Radix wallet to its original settings. All of your data and preferences will be erased. + Security Center status + Your wallet is recoverable + Your wallet is not recoverable + Your wallet is currently unrecoverable. If you do a factory reset now, you will never be able to access your Accounts and Personas again. + Once you’ve completed a factory reset, you will not be able to access your Accounts and Personas unless you do a full recovery. + Reset Wallet + Confirm factory reset + Return wallet to factory settings? You cannot undo this. + You need to write down a seed phrase + %s and %s are not recoverable. + %s and %s (plus some hidden) are not recoverable. + View and write down your seed phrase so Accounts and Personas are recoverable. + View and write down seed phrase + View and write down seed phrase + Personas are not recoverable + You need to write down a seed phrase + Problem with Configuration Backup + Your wallet is not recoverable + Automated Configuration Backup has stopped working. Check internet and cloud settings. + Automated Configuration Backup not working. Check internet connection and cloud settings. + Personas are not recoverable + Problem with Configuration Backup + Your wallet is not recoverable + Your wallet is not recoverable + Configuration Backup is not up to date. Create backup now. + To secure your wallet, turn on automated backups or manually export backup file. + Personas are not recoverable + Your wallet is not recoverable + Configuration Backup not up to date + Your wallet is not recoverable + Accounts and Personas not recoverable. Create Configuration Backup now. + Configuration Backup not up to date. Turn on automated backups or manually export backup file. + Personas are not recoverable + Configuration Backup not up to date + Recovery required + Recovery required + Enter seed phrase to recover control. + Enter seed phrase to recover control + Enter seed phrase to recover control + Recovery required + Recovery required + 1 account + %d accounts + 1 persona + %d personas - Connecting to %s - You’re connecting to %s for the first time - For this first connection, we’re going to run some quick automatic checks to verify the dApp. - Never show this screen again - Run all future verification checks automatically + dApp Request + You can proceed with this request after you create or restore your Radix Wallet. + Have you come from a genuine website? + Before you connect to %s, you might want to check: + Does the website address match what you\’re expecting? + If you came from a social media ad, is the website legitimate? + Switch back to your browser to continue diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 1cbe003cbd..f7cdb4b534 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -7,4 +7,5 @@ Inspect Profile Mobile Connect Delay Seconds Inspect Cloud Backups + No email client installed diff --git a/app/src/test/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepositoryTest.kt b/app/src/test/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepositoryTest.kt index c98448248d..264c2b61ee 100644 --- a/app/src/test/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepositoryTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/data/dapp/IncomingRequestRepositoryTest.kt @@ -1,8 +1,12 @@ package com.babylon.wallet.android.data.dapp import com.babylon.wallet.android.domain.model.IncomingMessage +import com.babylon.wallet.android.utils.AppEvent +import com.babylon.wallet.android.utils.AppEventBusImpl import com.radixdlt.sargon.NetworkId -import com.radixdlt.sargon.WalletInteractionId +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.spyk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -20,7 +24,8 @@ import java.util.UUID @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class) class IncomingRequestRepositoryTest { - private val incomingRequestRepository = IncomingRequestRepositoryImpl() + private val eventBus = spyk() + private val incomingRequestRepository = IncomingRequestRepositoryImpl(eventBus) private val amountOfIncomingRequests = 100 private val sampleIncomingRequest = IncomingMessage.IncomingRequest.AuthorizedRequest( remoteEntityId = IncomingMessage.RemoteEntityID.ConnectorId("remoteConnectorId"), @@ -78,18 +83,68 @@ class IncomingRequestRepositoryTest { } advanceUntilIdle() assertTrue(incomingRequestRepository.getAmountOfRequests() == 5) - assert(currentRequest?.interactionId.toString() == interactionIds[0].toString()) - incomingRequestRepository.requestHandled(interactionIds[0].toString()) - incomingRequestRepository.requestHandled(interactionIds[1].toString()) + assert(currentRequest?.interactionId.toString() == interactionIds[0]) + incomingRequestRepository.requestHandled(interactionIds[0]) + incomingRequestRepository.requestHandled(interactionIds[1]) advanceUntilIdle() - assert(currentRequest?.interactionId.toString() == interactionIds[2].toString()) + assert(currentRequest?.interactionId.toString() == interactionIds[2]) assertTrue(incomingRequestRepository.getAmountOfRequests() == 3) - incomingRequestRepository.requestHandled(interactionIds[2].toString()) - incomingRequestRepository.requestHandled(interactionIds[3].toString()) + incomingRequestRepository.requestHandled(interactionIds[2]) + incomingRequestRepository.requestHandled(interactionIds[3]) advanceUntilIdle() - assert(currentRequest?.interactionId.toString() == interactionIds[4].toString()) + assert(currentRequest?.interactionId.toString() == interactionIds[4]) assertTrue(incomingRequestRepository.getAmountOfRequests() == 1) - incomingRequestRepository.requestHandled(interactionIds[4].toString()) + incomingRequestRepository.requestHandled(interactionIds[4]) assertTrue(incomingRequestRepository.getAmountOfRequests() == 0) } + + @Test + fun `adding mobile connect request and deferring current makes mobile request new current, while deferred event stays in queue`() = + runTest { + var currentRequest: IncomingMessage.IncomingRequest? = null + val interactionId1 = UUID.randomUUID().toString() + val interactionId2 = UUID.randomUUID().toString() + incomingRequestRepository.currentRequestToHandle + .onEach { currentRequest = it } + .launchIn(CoroutineScope(UnconfinedTestDispatcher(testScheduler))) + incomingRequestRepository.add(incomingRequest = sampleIncomingRequest.copy(interactionId = interactionId1)) + advanceUntilIdle() + assertTrue(incomingRequestRepository.getAmountOfRequests() == 1) + assert(currentRequest?.interactionId == interactionId1) + incomingRequestRepository.addPriorityRequest(sampleIncomingRequest.copy(interactionId = interactionId2)) + incomingRequestRepository.requestDeferred(interactionId1) + advanceUntilIdle() + assert(currentRequest?.interactionId == interactionId2) + assertTrue(incomingRequestRepository.getAmountOfRequests() == 2) + coVerify(exactly = 1) { eventBus.sendEvent(AppEvent.DeferRequestHandling(interactionId1)) } + } + + @Test + fun `addFirst inserts item at 2nd position when there is high priority screen`() = runTest { + var currentRequest: IncomingMessage.IncomingRequest? = null + val interactionId1 = UUID.randomUUID().toString() + val interactionId2 = UUID.randomUUID().toString() + incomingRequestRepository.currentRequestToHandle + .onEach { currentRequest = it } + .launchIn(CoroutineScope(UnconfinedTestDispatcher(testScheduler))) + incomingRequestRepository.pauseIncomingRequests() + incomingRequestRepository.add(incomingRequest = sampleIncomingRequest.copy(interactionId = interactionId1)) + advanceUntilIdle() + assertTrue(incomingRequestRepository.getAmountOfRequests() == 1) + assert(currentRequest?.interactionId == null) + incomingRequestRepository.addPriorityRequest(sampleIncomingRequest.copy(interactionId = interactionId2)) + advanceUntilIdle() + incomingRequestRepository.resumeIncomingRequests() + advanceUntilIdle() + assert(currentRequest?.interactionId == interactionId2) + assertTrue(incomingRequestRepository.getAmountOfRequests() == 2) + } + + @Test + fun `reading buffered request clears it`() = runTest { + val request = mockk() + incomingRequestRepository.setBufferedRequest(request) + assert(incomingRequestRepository.consumeBufferedRequest() != null) + assert(incomingRequestRepository.consumeBufferedRequest() == null) + } } diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/ChooseAccountsViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/ChooseAccountsViewModelTest.kt index 88b8a64f3a..fdf6b11b98 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/ChooseAccountsViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/ChooseAccountsViewModelTest.kt @@ -7,10 +7,10 @@ import com.babylon.wallet.android.fakes.FakeProfileRepository import com.babylon.wallet.android.presentation.dapp.unauthorized.accountonetime.ARG_EXACT_ACCOUNT_COUNT import com.babylon.wallet.android.presentation.dapp.unauthorized.accountonetime.ARG_NUMBER_OF_ACCOUNTS import com.babylon.wallet.android.presentation.dapp.unauthorized.accountonetime.OneTimeChooseAccountsViewModel +import com.babylon.wallet.android.utils.AppEventBusImpl import com.radixdlt.sargon.Gateway import com.radixdlt.sargon.NetworkId import com.radixdlt.sargon.Profile -import com.radixdlt.sargon.WalletInteractionId import com.radixdlt.sargon.extensions.forNetwork import com.radixdlt.sargon.samples.sample import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -41,7 +41,7 @@ class ChooseAccountsViewModelTest { ) private val getProfileUseCase = GetProfileUseCase(profileRepository) - private val incomingRequestRepository = IncomingRequestRepositoryImpl() + private val incomingRequestRepository = IncomingRequestRepositoryImpl(AppEventBusImpl()) private lateinit var viewModel: OneTimeChooseAccountsViewModel diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/WalletViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/WalletViewModelTest.kt index 2a429eb9e1..a8b684fad5 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/WalletViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/WalletViewModelTest.kt @@ -3,6 +3,7 @@ package com.babylon.wallet.android.presentation import app.cash.turbine.test import com.babylon.wallet.android.NPSSurveyState import com.babylon.wallet.android.NPSSurveyStateObserver +import com.babylon.wallet.android.data.dapp.IncomingRequestRepository import com.babylon.wallet.android.data.repository.p2plink.P2PLinksRepository import com.babylon.wallet.android.domain.model.assets.AccountWithAssets import com.babylon.wallet.android.domain.usecases.GetEntitiesWithSecurityPromptUseCase @@ -30,9 +31,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import rdx.works.core.InstantGenerator import rdx.works.core.TimestampGenerator -import rdx.works.core.domain.cloudbackup.BackupState import rdx.works.core.domain.assets.Assets import rdx.works.core.domain.assets.Token +import rdx.works.core.domain.cloudbackup.BackupState import rdx.works.core.domain.resources.ExplicitMetadataKey import rdx.works.core.domain.resources.Resource import rdx.works.core.domain.resources.XrdResource @@ -60,6 +61,7 @@ class WalletViewModelTest : StateViewModelTest() { private val preferencesManager = mockk() private val appEventBus = mockk() private val testDispatcher = StandardTestDispatcher() + private val incomingRequestRepository = mockk() private val p2PLinksRepository = mockk() private val sampleProfile = Profile.sample() @@ -88,6 +90,7 @@ class WalletViewModelTest : StateViewModelTest() { override fun setUp() { super.setUp() + coEvery { incomingRequestRepository.consumeBufferedRequest() } returns null coEvery { ensureBabylonFactorSourceExistUseCase.babylonFactorSourceExist() } returns true every { getAccountsForSecurityPromptUseCase() } returns flow { emit(emptyList()) } every { getBackupStateUseCase() } returns flowOf( diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/dapp/login/DAppAuthorizedLoginViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/dapp/login/DAppAuthorizedLoginViewModelTest.kt index e6a71caa51..c53a311008 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/dapp/login/DAppAuthorizedLoginViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/dapp/login/DAppAuthorizedLoginViewModelTest.kt @@ -32,7 +32,6 @@ import com.radixdlt.sargon.PoolAddress import com.radixdlt.sargon.Profile import com.radixdlt.sargon.ResourceAddress import com.radixdlt.sargon.ValidatorAddress -import com.radixdlt.sargon.WalletInteractionId import com.radixdlt.sargon.extensions.AuthorizedDapps import com.radixdlt.sargon.extensions.Personas import com.radixdlt.sargon.extensions.ProfileEntity @@ -193,7 +192,6 @@ class DAppAuthorizedLoginViewModelTest : StateViewModelTest(ARG_INTERACTION_ID) } returns "1" coEvery { getCurrentGatewayUseCase() } returns Gateway.forNetwork(NetworkId.MAINNET) every { buildAuthorizedDappResponseUseCase.signingState } returns emptyFlow() coEvery { buildAuthorizedDappResponseUseCase.invoke(any(), any(), any(), any(), any(), any()) } returns Result.success(any()) coEvery { getProfileUseCase() } returns sampleProfile - coEvery { incomingRequestRepository.getAuthorizedRequest(any()) } returns requestWithNonExistingDappAddress + coEvery { incomingRequestRepository.getRequest(any()) } returns requestWithNonExistingDappAddress } @Test @@ -243,7 +242,7 @@ class DAppAuthorizedLoginViewModelTest : StateViewModelTest() { - private val incomingRequestRepository = IncomingRequestRepositoryImpl() + private val incomingRequestRepository = IncomingRequestRepositoryImpl(AppEventBusImpl()) private val getProfileUseCase = mockk() private val savedStateHandle = mockk() private val getDAppWithAssociatedResourcesUseCase = mockk() diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/settings/dappdetail/DappDetailViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/settings/dappdetail/DappDetailViewModelTest.kt index b8b88b0b6e..39bb021d7b 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/settings/dappdetail/DappDetailViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/settings/dappdetail/DappDetailViewModelTest.kt @@ -13,6 +13,7 @@ import com.babylon.wallet.android.presentation.settings.approveddapps.dappdetail import com.babylon.wallet.android.presentation.settings.approveddapps.dappdetail.DappDetailEvent import com.babylon.wallet.android.presentation.settings.approveddapps.dappdetail.DappDetailViewModel import com.babylon.wallet.android.presentation.settings.approveddapps.dappdetail.SelectedSheetState +import com.babylon.wallet.android.utils.AppEventBusImpl import com.radixdlt.sargon.AuthorizedDapp import com.radixdlt.sargon.AuthorizedPersonaSimple import com.radixdlt.sargon.Gateway @@ -50,7 +51,7 @@ import rdx.works.profile.domain.GetProfileUseCase @OptIn(ExperimentalCoroutinesApi::class) internal class DappDetailViewModelTest : StateViewModelTest() { - private val incomingRequestRepository = IncomingRequestRepositoryImpl() + private val incomingRequestRepository = IncomingRequestRepositoryImpl(AppEventBusImpl()) private val getProfileUseCase = mockk() private val savedStateHandle = mockk() private val getDAppWithAssociatedResourcesUseCase = mockk() diff --git a/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt b/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt index e3e8292376..d687acecbb 100644 --- a/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt +++ b/app/src/test/java/com/babylon/wallet/android/presentation/transaction/TransactionReviewViewModelTest.kt @@ -1,6 +1,7 @@ package com.babylon.wallet.android.presentation.transaction import androidx.lifecycle.SavedStateHandle +import app.cash.turbine.test import com.babylon.wallet.android.DefaultLocaleRule import com.babylon.wallet.android.data.dapp.IncomingRequestRepositoryImpl import com.babylon.wallet.android.data.gateway.coreapi.CoreApiTransactionReceipt @@ -53,7 +54,6 @@ import com.radixdlt.sargon.NetworkId import com.radixdlt.sargon.NewEntities import com.radixdlt.sargon.Profile import com.radixdlt.sargon.ResourceAddress -import com.radixdlt.sargon.WalletInteractionId import com.radixdlt.sargon.extensions.Curve25519SecretKey import com.radixdlt.sargon.extensions.forNetwork import com.radixdlt.sargon.extensions.rounded @@ -69,6 +69,7 @@ import io.mockk.just import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.slot +import junit.framework.TestCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first @@ -78,7 +79,6 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Ignore import org.junit.Rule @@ -114,9 +114,9 @@ internal class TransactionReviewViewModelTest : StateViewModelTest() private val resolveNotaryAndSignersUseCase = mockk() private val transactionRepository = mockk() - private val incomingRequestRepository = IncomingRequestRepositoryImpl() private val respondToIncomingRequestUseCase = mockk() private val appEventBus = mockk() + private val incomingRequestRepository = IncomingRequestRepositoryImpl(appEventBus) private val deviceCapabilityHelper = mockk() private val savedStateHandle = mockk() private val exceptionMessageProvider = mockk() @@ -295,7 +295,8 @@ internal class TransactionReviewViewModelTest : StateViewModelTest() private val respondToIncomingRequestUseCase = mockk() - private val appEventBus = mockk() + private val appEventBus = AppEventBusImpl() private val exceptionMessageProvider = mockk() private val signTransactionUseCase = mockk().apply { every { signingState } returns flowOf() @@ -106,10 +105,9 @@ internal class TransactionReviewViewModelTestExperimental : StateViewModelTest