diff --git a/RadixWallet/Clients/CloudBackupClient/CloudBackupClient+Live.swift b/RadixWallet/Clients/CloudBackupClient/CloudBackupClient+Live.swift index 21a794ed1a..8e8a880fc6 100644 --- a/RadixWallet/Clients/CloudBackupClient/CloudBackupClient+Live.swift +++ b/RadixWallet/Clients/CloudBackupClient/CloudBackupClient+Live.swift @@ -121,7 +121,7 @@ extension CloudBackupClient { @Sendable func backupProfileAndSaveResult(_ profile: Profile, existingRecord: CKRecord?) async throws { - try? userDefaults.setLastCloudBackup(.started(.now), of: profile) + try? userDefaults.setLastCloudBackup(.started(.now), of: profile.header) do { let json = profile.toJSONString() @@ -138,18 +138,25 @@ extension CloudBackupClient { failure = .other } - try? userDefaults.setLastCloudBackup(.failure(failure), of: profile) + try? userDefaults.setLastCloudBackup(.failure(failure), of: profile.header) throw error } - try? userDefaults.setLastCloudBackup(.success, of: profile) + try? userDefaults.setLastCloudBackup(.success, of: profile.header) } @Sendable func performAutomaticBackup(_ profile: Profile, timeToCheckIfClaimed: Bool) async { - let needsBackUp = profile.appPreferences.security.isCloudProfileSyncEnabled && profile.header.isNonEmpty let existingRecord = try? await fetchProfileRecord(profile.id) let backedUpHeader = try? existingRecord.map(getProfileHeader) + + if let backedUpHeader, let backupDate = existingRecord?.modificationDate { + try? userDefaults.setLastCloudBackup(.success, of: backedUpHeader, at: backupDate) + } else { + try? userDefaults.removeLastCloudBackup(for: profile.id) + } + + let needsBackUp = profile.appPreferences.security.isCloudProfileSyncEnabled && profile.header.isNonEmpty let isBackedUp = backedUpHeader?.saveIdentifier == profile.header.saveIdentifier let shouldBackUp = needsBackUp && !isBackedUp diff --git a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Interface.swift b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Interface.swift index e5549b5ddb..47f7a4d4ba 100644 --- a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Interface.swift +++ b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Interface.swift @@ -12,10 +12,10 @@ public struct DeviceFactorSourceClient: Sendable { /// Fetched accounts and personas on current network that are controlled by a device factor source, for every factor source in current profile public var controlledEntities: GetControlledEntities - /// The entities (`Accounts` & `Personas`) that are problematic. This is, that either: + /// The entities (`Accounts` & `Personas`) that are in bad state. This is, that either: /// - their mnmemonic is missing (entity was imported but seed phrase never entered), or /// - their mnmemonic is not backed up (entity was created but seed phrase never written down). - public var problematicEntities: ProblematicEntities + public var entitiesInBadState: EntitiesInBadState public init( publicKeysFromOnDeviceHD: @escaping PublicKeysFromOnDeviceHD, @@ -23,14 +23,14 @@ public struct DeviceFactorSourceClient: Sendable { isAccountRecoveryNeeded: @escaping IsAccountRecoveryNeeded, entitiesControlledByFactorSource: @escaping GetEntitiesControlledByFactorSource, controlledEntities: @escaping GetControlledEntities, - problematicEntities: @escaping ProblematicEntities + entitiesInBadState: @escaping EntitiesInBadState ) { self.publicKeysFromOnDeviceHD = publicKeysFromOnDeviceHD self.signatureFromOnDeviceHD = signatureFromOnDeviceHD self.isAccountRecoveryNeeded = isAccountRecoveryNeeded self.entitiesControlledByFactorSource = entitiesControlledByFactorSource self.controlledEntities = controlledEntities - self.problematicEntities = problematicEntities + self.entitiesInBadState = entitiesInBadState } } @@ -42,7 +42,7 @@ extension DeviceFactorSourceClient { public typealias PublicKeysFromOnDeviceHD = @Sendable (PublicKeysFromOnDeviceHDRequest) async throws -> [HierarchicalDeterministicPublicKey] public typealias SignatureFromOnDeviceHD = @Sendable (SignatureFromOnDeviceHDRequest) async throws -> SignatureWithPublicKey public typealias IsAccountRecoveryNeeded = @Sendable () async throws -> Bool - public typealias ProblematicEntities = @Sendable () async throws -> AnyAsyncSequence<(mnemonicMissing: ProblematicAddresses, unrecoverable: ProblematicAddresses)> + public typealias EntitiesInBadState = @Sendable () async throws -> AnyAsyncSequence<(withoutControl: AddressesOfEntitiesInBadState, unrecoverable: AddressesOfEntitiesInBadState)> } // MARK: - DiscrepancyUnsupportedCurve @@ -241,8 +241,8 @@ extension SigningPurpose { // MARK: - FactorInstanceDoesNotHaveADerivationPathUnableToSign struct FactorInstanceDoesNotHaveADerivationPathUnableToSign: Swift.Error {} -// MARK: - ProblematicAddresses -public struct ProblematicAddresses: Sendable, Hashable { +// MARK: - AddressesOfEntitiesInBadState +public struct AddressesOfEntitiesInBadState: Sendable, Hashable { let accounts: [AccountAddress] let hiddenAccounts: [AccountAddress] let personas: [IdentityAddress] diff --git a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Live.swift b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Live.swift index e5feeaceb3..33edf4b8c2 100644 --- a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Live.swift +++ b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Live.swift @@ -73,33 +73,33 @@ extension DeviceFactorSourceClient: DependencyKey { ) } - struct FactorSourceHasMnemonic: Sendable, Equatable { + struct KeychainPresenceOfMnemonic: Sendable, Equatable { let id: FactorSourceIDFromHash let present: Bool } @Sendable - func factorSourcesMnemonicPresence() async -> AnyAsyncSequence<[FactorSourceHasMnemonic]> { + func factorSourcesMnemonicPresence() async -> AnyAsyncSequence<[KeychainPresenceOfMnemonic]> { await combineLatest(profileStore.factorSourcesValues(), secureStorageClient.keychainChanged().prepend(())) .map { factorSources, _ in factorSources .compactMap { $0.extract(DeviceFactorSource.self)?.id } .map { id in - FactorSourceHasMnemonic(id: id, present: secureStorageClient.containsMnemonicIdentifiedByFactorSourceID(id)) + KeychainPresenceOfMnemonic(id: id, present: secureStorageClient.containsMnemonicIdentifiedByFactorSourceID(id)) } } .removeDuplicates() .eraseToAnyAsyncSequence() } - let problematicEntities: @Sendable () async throws -> AnyAsyncSequence<(mnemonicMissing: ProblematicAddresses, unrecoverable: ProblematicAddresses)> = { - await combineLatest(factorSourcesMnemonicPresence(), userDefaults.factorSourceIDOfBackedUpMnemonics(), profileStore.values()).map { factorSources, backedUpFactorSources, profile in + let entitiesInBadState: @Sendable () async throws -> AnyAsyncSequence<(withoutControl: AddressesOfEntitiesInBadState, unrecoverable: AddressesOfEntitiesInBadState)> = { + await combineLatest(factorSourcesMnemonicPresence(), userDefaults.factorSourceIDOfBackedUpMnemonics(), profileStore.values()).map { presencesOfMnemonics, backedUpFactorSources, profile in - let mnemonicMissingFactorSources = factorSources + let mnemonicMissingFactorSources = presencesOfMnemonics .filter(not(\.present)) .map(\.id) - let mnemomincPresentFactorSources = factorSources + let mnemomincPresentFactorSources = presencesOfMnemonics .filter(\.present) .map(\.id) @@ -112,49 +112,35 @@ extension DeviceFactorSourceClient: DependencyKey { let personas = network.getPersonas() let hiddenPersonas = network.getHiddenPersonas() - func mnemonicMissing(_ account: Account) -> Bool { - switch account.securityState { + func withoutControl(_ entity: some EntityProtocol) -> Bool { + switch entity.securityState { case let .unsecured(value): mnemonicMissingFactorSources.contains(value.transactionSigning.factorSourceId) } } - func mnemonicMissing(_ persona: Persona) -> Bool { - switch persona.securityState { - case let .unsecured(value): - mnemonicMissingFactorSources.contains(value.transactionSigning.factorSourceId) - } - } - - func unrecoverable(_ account: Account) -> Bool { - switch account.securityState { - case let .unsecured(value): - unrecoverableFactorSources.contains(value.transactionSigning.factorSourceId) - } - } - - func unrecoverable(_ persona: Persona) -> Bool { - switch persona.securityState { + func unrecoverable(_ entity: some EntityProtocol) -> Bool { + switch entity.securityState { case let .unsecured(value): unrecoverableFactorSources.contains(value.transactionSigning.factorSourceId) } } - let mnemonicMissing = ProblematicAddresses( - accounts: accounts.filter(mnemonicMissing(_:)).map(\.address), - hiddenAccounts: hiddenAccounts.filter(mnemonicMissing(_:)).map(\.address), - personas: personas.filter(mnemonicMissing(_:)).map(\.address), - hiddenPersonas: hiddenPersonas.filter(mnemonicMissing(_:)).map(\.address) + let withoutControl = AddressesOfEntitiesInBadState( + accounts: accounts.filter(withoutControl(_:)).map(\.address), + hiddenAccounts: hiddenAccounts.filter(withoutControl(_:)).map(\.address), + personas: personas.filter(withoutControl(_:)).map(\.address), + hiddenPersonas: hiddenPersonas.filter(withoutControl(_:)).map(\.address) ) - let unrecoverable = ProblematicAddresses( + let unrecoverable = AddressesOfEntitiesInBadState( accounts: accounts.filter(unrecoverable(_:)).map(\.address), hiddenAccounts: hiddenAccounts.filter(unrecoverable(_:)).map(\.address), personas: personas.filter(unrecoverable(_:)).map(\.address), hiddenPersonas: hiddenPersonas.filter(unrecoverable(_:)).map(\.address) ) - return (mnemonicMissing: mnemonicMissing, unrecoverable: unrecoverable) + return (withoutControl: withoutControl, unrecoverable: unrecoverable) } .eraseToAnyAsyncSequence() } @@ -219,7 +205,7 @@ extension DeviceFactorSourceClient: DependencyKey { try await entitiesControlledByFactorSource($0, maybeOverridingSnapshot) }) }, - problematicEntities: problematicEntities + entitiesInBadState: entitiesInBadState ) } } diff --git a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Test.swift b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Test.swift index d15f1d374e..d0544f324e 100644 --- a/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Test.swift +++ b/RadixWallet/Clients/DeviceFactorSourceClient/DeviceFactorSourceClient+Test.swift @@ -16,7 +16,7 @@ extension DeviceFactorSourceClient: TestDependencyKey { isAccountRecoveryNeeded: { false }, entitiesControlledByFactorSource: { _, _ in throw NoopError() }, controlledEntities: { _ in [] }, - problematicEntities: { throw NoopError() } + entitiesInBadState: { throw NoopError() } ) public static let testValue = Self( @@ -25,11 +25,11 @@ extension DeviceFactorSourceClient: TestDependencyKey { isAccountRecoveryNeeded: unimplemented("\(Self.self).isAccountRecoveryNeeded"), entitiesControlledByFactorSource: unimplemented("\(Self.self).entitiesControlledByFactorSource"), controlledEntities: unimplemented("\(Self.self).controlledEntities"), - problematicEntities: unimplemented("\(Self.self).problematicEntities") + entitiesInBadState: unimplemented("\(Self.self).entitiesInBadState") ) } -private extension ProblematicAddresses { +private extension AddressesOfEntitiesInBadState { static var empty: Self { .init(accounts: [], hiddenAccounts: [], personas: [], hiddenPersonas: []) } diff --git a/RadixWallet/Clients/FactorSourcesClient/FactorSourcesClient+Live.swift b/RadixWallet/Clients/FactorSourcesClient/FactorSourcesClient+Live.swift index 0b66a15bda..f0fc339e5c 100644 --- a/RadixWallet/Clients/FactorSourcesClient/FactorSourcesClient+Live.swift +++ b/RadixWallet/Clients/FactorSourcesClient/FactorSourcesClient+Live.swift @@ -421,7 +421,6 @@ extension FactorSourceKind: Comparable { case .offDeviceMnemonic: 1 case .securityQuestions: 2 case .trustedContact: 3 - // we want to sign with device last, since it would allow for us to stop using // ephemeral notary and allow us to implement a AutoPurgingMnemonicCache which // deletes items after 1 sec, thus `device` must come last. diff --git a/RadixWallet/Clients/KeychainClient/KeychainClient+Interface.swift b/RadixWallet/Clients/KeychainClient/KeychainClient+Interface.swift index b76dfdd31c..8f356477b2 100644 --- a/RadixWallet/Clients/KeychainClient/KeychainClient+Interface.swift +++ b/RadixWallet/Clients/KeychainClient/KeychainClient+Interface.swift @@ -18,6 +18,10 @@ public struct KeychainClient: Sendable { public var _getAllKeysMatchingAttributes: GetAllKeysMatchingAttributes public var _keychainChanged: KeychainChanged + /// This a _best effort_ publisher that will emit a change every time the Keychain is changed due to actions inside the Wallet app. + /// However, we cannot detect external changes (e.g. Keychain getting wiped when passcode is deleted). + public var _keychainChanged: KeychainChanged + public init( getServiceAndAccessGroup: @escaping GetServiceAndAccessGroup, containsDataForKey: @escaping ContainsDataForKey, diff --git a/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Interface.swift b/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Interface.swift index 3a5b719fd6..4d485f70a0 100644 --- a/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Interface.swift +++ b/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Interface.swift @@ -24,7 +24,7 @@ extension SecurityCenterClient { public enum SecurityProblem: Hashable, Sendable, Identifiable { /// The given addresses of `accounts` and `personas` are unrecoverable if the user loses their phone, since their corresponding seed phrase has not been written down. /// NOTE: This definition differs from the one at Confluence since we don't have shields implemented yet. - case problem3(addresses: ProblematicAddresses) + case problem3(addresses: AddressesOfEntitiesInBadState) /// Wallet backups to the cloud aren’t working (wallet tried to do a backup and it didn’t work within, say, 5 minutes.) /// This means that currently all accounts and personas are at risk of being practically unrecoverable if the user loses their phone. /// Also they would lose all of their other non-security wallet settings and data. @@ -38,7 +38,7 @@ public enum SecurityProblem: Hashable, Sendable, Identifiable { case problem7 /// User has gotten a new phone (and restored their wallet from backup) and the wallet sees that there are accounts without shields using a phone key, /// meaning they can only be recovered with the seed phrase. (See problem 2) This would also be the state if a user disabled their PIN (and reenabled it), clearing phone keys. - case problem9(addresses: ProblematicAddresses) + case problem9(addresses: AddressesOfEntitiesInBadState) public var id: Int { number } @@ -76,7 +76,7 @@ public enum SecurityProblem: Hashable, Sendable, Identifiable { } } - private func problem3(addresses: ProblematicAddresses) -> String { + private func problem3(addresses: AddressesOfEntitiesInBadState) -> String { typealias Common = L10n.SecurityProblems.Common typealias Problem = L10n.SecurityProblems.No3 let hasHidden = addresses.hiddenAccounts.count + addresses.hiddenPersonas.count > 0 diff --git a/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Live.swift b/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Live.swift index d17ec5fc18..eda93b1564 100644 --- a/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Live.swift +++ b/RadixWallet/Clients/SecurityCenterClient/SecurityCenterClient+Live.swift @@ -55,14 +55,14 @@ extension SecurityCenterClient { @Sendable func startMonitoring() async throws { let profileValues = await profileStore.values() - let problematicValues = try await deviceFactorSourceClient.problematicEntities() + let entitiesInBadState = try await deviceFactorSourceClient.entitiesInBadState() let backupValues = await combineLatest(cloudBackups(), manualBackups()).map { (cloud: $0, manual: $1) } - for try await (profile, problematic, backups) in combineLatest(profileValues, problematicValues, backupValues) { + for try await (profile, entitiesInBadState, backups) in combineLatest(profileValues, entitiesInBadState, backupValues) { let isCloudProfileSyncEnabled = profile.appPreferences.security.isCloudProfileSyncEnabled - func hasProblem3() async -> ProblematicAddresses? { - problematic.unrecoverable.isEmpty ? nil : problematic.unrecoverable + func hasProblem3() async -> AddressesOfEntitiesInBadState? { + entitiesInBadState.unrecoverable.isEmpty ? nil : entitiesInBadState.unrecoverable } func hasProblem5() -> Bool { @@ -81,8 +81,8 @@ extension SecurityCenterClient { !isCloudProfileSyncEnabled && backups.manual?.isCurrent == false } - func hasProblem9() async -> ProblematicAddresses? { - problematic.mnemonicMissing.isEmpty ? nil : problematic.mnemonicMissing + func hasProblem9() async -> AddressesOfEntitiesInBadState? { + entitiesInBadState.withoutControl.isEmpty ? nil : entitiesInBadState.withoutControl } var result: [SecurityProblem] = [] @@ -117,7 +117,7 @@ extension SecurityCenterClient { } } -private extension ProblematicAddresses { +private extension AddressesOfEntitiesInBadState { var isEmpty: Bool { accounts.count + hiddenAccounts.count + personas.count == 0 } diff --git a/RadixWallet/Clients/TransactionClient/Models/TransactionFailure.swift b/RadixWallet/Clients/TransactionClient/Models/TransactionFailure.swift index 154bf0928e..0f0c715f9d 100644 --- a/RadixWallet/Clients/TransactionClient/Models/TransactionFailure.swift +++ b/RadixWallet/Clients/TransactionClient/Models/TransactionFailure.swift @@ -47,6 +47,7 @@ extension TransactionFailure { case .failedToSignIntentWithAccountSigners, .failedToSignSignedCompiledIntentWithNotarySigner, .failedToConvertNotarySignature, .failedToConvertAccountSignatures: (errorKind: .failedToSignTransaction, message: nil) } + case .failedToSubmit: (errorKind: .failedToSubmitTransaction, message: nil) diff --git a/RadixWallet/Clients/TransportProfileClient/TransportProfileClient+Interface.swift b/RadixWallet/Clients/TransportProfileClient/TransportProfileClient+Interface.swift index 003554649e..b3459e1a01 100644 --- a/RadixWallet/Clients/TransportProfileClient/TransportProfileClient+Interface.swift +++ b/RadixWallet/Clients/TransportProfileClient/TransportProfileClient+Interface.swift @@ -20,7 +20,7 @@ public struct TransportProfileClient: Sendable { } extension TransportProfileClient { - public typealias ImportProfile = @Sendable (Profile, Set, Bool) async throws -> Void + public typealias ImportProfile = @Sendable (Profile, Set, _ containsP2PLinks: Bool) async throws -> Void public typealias ProfileForExport = @Sendable () async throws -> Profile public typealias DidExportProfile = @Sendable (Profile) throws -> Void } diff --git a/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift b/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift index dc20e16f37..3aff79a557 100644 --- a/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift +++ b/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift @@ -165,13 +165,12 @@ extension UserDefaults.Dependency { try save(codable: backups, forKey: .lastCloudBackups) } - public func setLastCloudBackup(_ result: BackupResult.Result, of profile: Profile) throws { + public func setLastCloudBackup(_ result: BackupResult.Result, of header: Profile.Header, at date: Date = .now) throws { var backups: [UUID: BackupResult] = getLastCloudBackups - let now = Date.now - let lastSuccess = result == .success ? now : backups[profile.id]?.lastSuccess - backups[profile.id] = .init( - date: now, - saveIdentifier: profile.header.saveIdentifier, + let lastSuccess = result == .success ? date : backups[header.id]?.lastSuccess + backups[header.id] = .init( + date: date, + saveIdentifier: header.saveIdentifier, result: result, lastSuccess: lastSuccess ) diff --git a/RadixWallet/Core/DesignSystem/Components/Thumbnails.swift b/RadixWallet/Core/DesignSystem/Components/Thumbnails.swift index f35c91e444..3c71aa3c1a 100644 --- a/RadixWallet/Core/DesignSystem/Components/Thumbnails.swift +++ b/RadixWallet/Core/DesignSystem/Components/Thumbnails.swift @@ -255,7 +255,6 @@ public struct LoadableImage: View { case .shimmer: Color.app.gray4 .shimmer(active: true, config: .accountResourcesLoading) - case let .color(color): color case let .asset(imageAsset): diff --git a/RadixWallet/Core/SharedModels/P2P/Application/IncomingMessage/P2P+RTCIncomingMessage.swift b/RadixWallet/Core/SharedModels/P2P/Application/IncomingMessage/P2P+RTCIncomingMessage.swift index 5351f9f29b..ebd35d66f4 100644 --- a/RadixWallet/Core/SharedModels/P2P/Application/IncomingMessage/P2P+RTCIncomingMessage.swift +++ b/RadixWallet/Core/SharedModels/P2P/Application/IncomingMessage/P2P+RTCIncomingMessage.swift @@ -52,6 +52,7 @@ extension P2P.RTCIncomingMessageContainer { case let (.failure(lhsFailure), .failure(rhsFailure)): // FIXME: strongly type messages? to an Error type which is Hashable? return String(describing: lhsFailure) == String(describing: rhsFailure) + case let (.success(lhsSuccess), .success(rhsSuccess)): return lhsSuccess == rhsSuccess diff --git a/RadixWallet/Core/SharedModels/P2P/ConnectorExtension/P2P+ConnectorExtension+Response.swift b/RadixWallet/Core/SharedModels/P2P/ConnectorExtension/P2P+ConnectorExtension+Response.swift index ae34876b86..620d30f189 100644 --- a/RadixWallet/Core/SharedModels/P2P/ConnectorExtension/P2P+ConnectorExtension+Response.swift +++ b/RadixWallet/Core/SharedModels/P2P/ConnectorExtension/P2P+ConnectorExtension+Response.swift @@ -163,7 +163,6 @@ extension P2P.ConnectorExtension.Response.LedgerHardwareWallet { self.response = try decodeResponse { Success.signChallenge($0) } - case .deriveAndDisplayAddress: self.response = try decodeResponse { Success.deriveAndDisplayAddress($0) diff --git a/RadixWallet/Features/AccountPreferencesFeature/Children/DevAccountPreferences+Reducer.swift b/RadixWallet/Features/AccountPreferencesFeature/Children/DevAccountPreferences+Reducer.swift index 4f09c8abcc..280bd5acbe 100644 --- a/RadixWallet/Features/AccountPreferencesFeature/Children/DevAccountPreferences+Reducer.swift +++ b/RadixWallet/Features/AccountPreferencesFeature/Children/DevAccountPreferences+Reducer.swift @@ -219,6 +219,7 @@ public struct DevAccountPreferences: Sendable, FeatureReducer { case let .canCreateAuthSigningKey(canCreateAuthSigningKey): state.canCreateAuthSigningKey = canCreateAuthSigningKey return .none + case let .canTurnIntoDappDefAccountType(canTurnIntoDappDefAccountType): state.canTurnIntoDappDefinitionAccountType = canTurnIntoDappDefAccountType return .none diff --git a/RadixWallet/Features/AccountRecoveryScan/Coordinator/AccountRecoveryScanCoordinator.swift b/RadixWallet/Features/AccountRecoveryScan/Coordinator/AccountRecoveryScanCoordinator.swift index b8528dc257..a37926bdd2 100644 --- a/RadixWallet/Features/AccountRecoveryScan/Coordinator/AccountRecoveryScanCoordinator.swift +++ b/RadixWallet/Features/AccountRecoveryScan/Coordinator/AccountRecoveryScanCoordinator.swift @@ -132,6 +132,7 @@ public struct AccountRecoveryScanCoordinator: Sendable, FeatureReducer { let childState = state.backTo ?? AccountRecoveryScanCoordinator.State.accountRecoveryScanInProgressState(purpose: state.purpose) state.root = .accountRecoveryScanInProgress(childState) return .none + case let .selectInactiveAccountsToAdd(.delegate(.finished(selectedInactive, active))): return completed(purpose: state.purpose, active: active, inactive: selectedInactive) diff --git a/RadixWallet/Features/AppFeature/App+Reducer.swift b/RadixWallet/Features/AppFeature/App+Reducer.swift index 30392e9e98..d56c5805a2 100644 --- a/RadixWallet/Features/AppFeature/App+Reducer.swift +++ b/RadixWallet/Features/AppFeature/App+Reducer.swift @@ -96,6 +96,7 @@ public struct App: Sendable, FeatureReducer { } else { goToMain(state: &state) } + default: .none } diff --git a/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift b/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift index e6ce6c154c..2a2d1f30ea 100644 --- a/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift +++ b/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift @@ -268,7 +268,6 @@ func needsSignatureForDepositting( return false case (.acceptAll, .deny): return true - // Accept Known case (.acceptKnown, .allow): return false @@ -283,7 +282,6 @@ func needsSignatureForDepositting( return !hasResource case (.acceptKnown, .deny): return true - // DenyAll case (.denyAll, .none): return true diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift index 86d66b05d6..db042451e9 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift @@ -315,7 +315,6 @@ private extension GatewayAPI.ProgrammaticScryptoSborValue { switch self { case .array, .map, .mapEntry, .tuple: .complex - case let .bool(content): .primitive(String(content.value)) case let .bytes(content): diff --git a/RadixWallet/Features/CreateAccount/Coordinator/CreateAccountCoordinator+Models.swift b/RadixWallet/Features/CreateAccount/Coordinator/CreateAccountCoordinator+Models.swift index 7036b0c11e..0f95b3f375 100644 --- a/RadixWallet/Features/CreateAccount/Coordinator/CreateAccountCoordinator+Models.swift +++ b/RadixWallet/Features/CreateAccount/Coordinator/CreateAccountCoordinator+Models.swift @@ -52,7 +52,6 @@ extension CreateAccountConfig { navigationButtonCTA: .goBackToChooseAccounts, specificNetworkID: nil ) - case .newAccountFromHome: self.init( isFirstAccount: false, diff --git a/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionFlow.swift b/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionFlow.swift index 643f420fca..8a83379fc5 100644 --- a/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionFlow.swift +++ b/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionFlow.swift @@ -839,6 +839,7 @@ extension DappInteractionFlow.Path.State { switch anyItem { case .remote(.auth(.usePersona)): return nil + case let .remote(.auth(.login(loginRequest))): self.state = .login(.init( dappMetadata: dappMetadata, diff --git a/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionModels.swift b/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionModels.swift index c19461914b..edc87aeddf 100644 --- a/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionModels.swift +++ b/RadixWallet/Features/DappInteractionFeature/Coordinator/DappInteractionModels.swift @@ -127,7 +127,6 @@ extension P2P.Dapp.Request { 3 case .oneTimePersonaData: 4 - // transactions case .send: 0 diff --git a/RadixWallet/Features/DappInteractionFeature/Interactor/DappInteractor.swift b/RadixWallet/Features/DappInteractionFeature/Interactor/DappInteractor.swift index 57f802195f..b52cbfb9ff 100644 --- a/RadixWallet/Features/DappInteractionFeature/Interactor/DappInteractor.swift +++ b/RadixWallet/Features/DappInteractionFeature/Interactor/DappInteractor.swift @@ -120,6 +120,7 @@ struct DappInteractor: Sendable, FeatureReducer { switch viewAction { case .task: return handleIncomingRequests() + case let .responseFailureAlert(action): switch action { case .dismiss: @@ -150,6 +151,7 @@ struct DappInteractor: Sendable, FeatureReducer { return .run { _ in await radixConnectClient.disconnectAll() } + case .moveToForeground: return .run { _ in _ = await radixConnectClient.loadP2PLinksAndConnectAll() diff --git a/RadixWallet/Features/DappsAndPersonas/AuthorizedDApps/AuthorizedDApps.swift b/RadixWallet/Features/DappsAndPersonas/AuthorizedDApps/AuthorizedDApps.swift index ecae9949b3..16e52fa406 100644 --- a/RadixWallet/Features/DappsAndPersonas/AuthorizedDApps/AuthorizedDApps.swift +++ b/RadixWallet/Features/DappsAndPersonas/AuthorizedDApps/AuthorizedDApps.swift @@ -113,9 +113,11 @@ public struct AuthorizedDappsFeature: Sendable, FeatureReducer { case let .loadedDapps(.failure(error)): errorQueue.schedule(error) return .none + case let .presentDappDetails(presentedDappState): state.destination = .presentedDapp(presentedDappState) return .none + case let .loadedThumbnail(thumbnail, dApp: id): state.thumbnails[id] = thumbnail return .none diff --git a/RadixWallet/Features/DisplayEntitiesControlledByMnemonic/DisplayEntitiesControlledByMnemonic+Reducer.swift b/RadixWallet/Features/DisplayEntitiesControlledByMnemonic/DisplayEntitiesControlledByMnemonic+Reducer.swift index 08b5573cbf..72c3bf40ff 100644 --- a/RadixWallet/Features/DisplayEntitiesControlledByMnemonic/DisplayEntitiesControlledByMnemonic+Reducer.swift +++ b/RadixWallet/Features/DisplayEntitiesControlledByMnemonic/DisplayEntitiesControlledByMnemonic+Reducer.swift @@ -134,7 +134,7 @@ private extension [SecurityProblem] { } } -private extension ProblematicAddresses { +private extension AddressesOfEntitiesInBadState { var problematicAccounts: Set { Set(accounts + hiddenAccounts) } diff --git a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift index 53f8ee5dee..a4fee67120 100644 --- a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift +++ b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift @@ -183,6 +183,7 @@ public struct Home: Sendable, FeatureReducer { )) ) return .none + case .pullToRefreshStarted: let accountAddresses = state.accounts.map(\.address) return .run { _ in @@ -190,6 +191,7 @@ public struct Home: Sendable, FeatureReducer { } catch: { error, _ in errorQueue.schedule(error) } + case .radixBannerButtonTapped: return .run { _ in await openURL(Home.radixBannerURL) @@ -247,11 +249,13 @@ public struct Home: Sendable, FeatureReducer { } #endif return .none + case let .shouldShowNPSSurvey(shouldShow): if shouldShow { state.addDestination(.npsSurvey(.init())) } return .none + case let .accountsFiatWorthLoaded(fiatWorths): state.accountRows.mutateAll { if let fiatWorth = fiatWorths[$0.id] { @@ -260,6 +264,7 @@ public struct Home: Sendable, FeatureReducer { } state.totalFiatWorth = state.accountRows.map(\.totalFiatWorth).reduce(+) ?? .loading return .none + case .showLinkConnectorIfNeeded: let purpose: NewConnectionApproval.State.Purpose? = if userDefaults.showRelinkConnectorsAfterProfileRestore { .approveRelinkAfterProfileRestore diff --git a/RadixWallet/Features/ImportMnemonic/ImportWord/ImportMnemonicWord.swift b/RadixWallet/Features/ImportMnemonic/ImportWord/ImportMnemonicWord.swift index 12cadfb73c..3100015fa4 100644 --- a/RadixWallet/Features/ImportMnemonic/ImportWord/ImportMnemonicWord.swift +++ b/RadixWallet/Features/ImportMnemonic/ImportWord/ImportMnemonicWord.swift @@ -157,6 +157,7 @@ public struct ImportMnemonicWord: Sendable, FeatureReducer { candidate, fromPartial: state.value.text ))) + case let .textFieldFocused(field): state.focusedField = field return field == nil ? .send(.delegate(.lostFocus(displayText: state.value.text))) : .none diff --git a/RadixWallet/Features/NewConnectionFeature/Coordinator/NewConnection+Reducer.swift b/RadixWallet/Features/NewConnectionFeature/Coordinator/NewConnection+Reducer.swift index 6b896bb95e..2b7039749a 100644 --- a/RadixWallet/Features/NewConnectionFeature/Coordinator/NewConnection+Reducer.swift +++ b/RadixWallet/Features/NewConnectionFeature/Coordinator/NewConnection+Reducer.swift @@ -318,7 +318,7 @@ extension AlertState { TextState(L10n.Common.dismiss) } } message: { - TextState(L10n.LinkedConnectors.incorrectQrMessage) + TextState(L10n.LinkedConnectors.oldQRErrorMessage) } } } diff --git a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/ImportSeedPhrasesFlow/ImportMnemonicControllingAccounts.swift b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/ImportSeedPhrasesFlow/ImportMnemonicControllingAccounts.swift index 9150c5f514..b9852ef472 100644 --- a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/ImportSeedPhrasesFlow/ImportMnemonicControllingAccounts.swift +++ b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/ImportSeedPhrasesFlow/ImportMnemonicControllingAccounts.swift @@ -124,6 +124,7 @@ public struct ImportMnemonicControllingAccounts: Sendable, FeatureReducer { switch viewAction { case .appeared: return .none + case .inputMnemonicButtonTapped: state.destination = .importMnemonic(.init( warning: L10n.RevealSeedPhrase.warning, diff --git a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/RecoverWalletWithoutProfile/Coordinator/RecoverWalletWithoutProfileCoordinator+View.swift b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/RecoverWalletWithoutProfile/Coordinator/RecoverWalletWithoutProfileCoordinator+View.swift index 05300cf406..abced842bb 100644 --- a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/RecoverWalletWithoutProfile/Coordinator/RecoverWalletWithoutProfileCoordinator+View.swift +++ b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Children/RecoverWalletWithoutProfile/Coordinator/RecoverWalletWithoutProfileCoordinator+View.swift @@ -46,7 +46,6 @@ public extension RecoverWalletWithoutProfileCoordinator { action: RecoverWalletWithoutProfileCoordinator.Path.Action.importMnemonic, then: { ImportMnemonic.View(store: $0) } ) - case .recoveryComplete: CaseLet( /RecoverWalletWithoutProfileCoordinator.Path.State.recoveryComplete, diff --git a/RadixWallet/Features/ProfileBackupsFeature/Shared/EncryptOrDecryptProfile+Reducer.swift b/RadixWallet/Features/ProfileBackupsFeature/Shared/EncryptOrDecryptProfile+Reducer.swift index 8877cdfbbc..9f1825e720 100644 --- a/RadixWallet/Features/ProfileBackupsFeature/Shared/EncryptOrDecryptProfile+Reducer.swift +++ b/RadixWallet/Features/ProfileBackupsFeature/Shared/EncryptOrDecryptProfile+Reducer.swift @@ -120,6 +120,7 @@ public struct EncryptOrDecryptProfile: Sendable, FeatureReducer { case .encryptSpecific: break + case .decrypt: break } diff --git a/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift b/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift index b15697d025..1277a2f3db 100644 --- a/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift +++ b/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift @@ -96,13 +96,10 @@ extension UserDefaults.Dependency.Key { return [value] case .epochForWhenLastUsedByAccountAddress: return userDefaults.loadEpochForWhenLastUsedByAccountAddress().epochForAccounts.map { "epoch: \($0.epoch) account: \($0.accountAddress)" } - case .hideMigrateOlympiaButton: return [userDefaults.hideMigrateOlympiaButton].map(String.init(describing:)) - case .showRadixBanner: return [userDefaults.showRadixBanner].map(String.init(describing:)) - case .mnemonicsUserClaimsToHaveBackedUp: return userDefaults.getFactorSourceIDOfBackedUpMnemonics().map(String.init(describing:)) case .transactionsCompletedCounter: diff --git a/RadixWallet/Features/SettingsFeature/DisplayMnemonics/Coordinator/DisplayMnemonics.swift b/RadixWallet/Features/SettingsFeature/DisplayMnemonics/Coordinator/DisplayMnemonics.swift index 23aabcbbe0..dc4c939383 100644 --- a/RadixWallet/Features/SettingsFeature/DisplayMnemonics/Coordinator/DisplayMnemonics.swift +++ b/RadixWallet/Features/SettingsFeature/DisplayMnemonics/Coordinator/DisplayMnemonics.swift @@ -159,6 +159,7 @@ public struct DisplayMnemonics: Sendable, FeatureReducer { return .none } + default: return .none } diff --git a/RadixWallet/Features/Signing/Coordinator/Signing.swift b/RadixWallet/Features/Signing/Coordinator/Signing.swift index 529619d6fa..20d0794994 100644 --- a/RadixWallet/Features/Signing/Coordinator/Signing.swift +++ b/RadixWallet/Features/Signing/Coordinator/Signing.swift @@ -111,6 +111,7 @@ public struct Signing: Sendable, FeatureReducer { loggerGlobal.error("Failed to notarize transaction, error: \(error)") errorQueue.schedule(error) return .none + case let .notarizeResult(.success(notarized)): switch state.signingPurposeWithPayload { case .signAuth: @@ -135,6 +136,7 @@ public struct Signing: Sendable, FeatureReducer { case .signWithFactorSource(.delegate(.cancel)): return .send(.delegate(.cancelSigning)) + default: return .none } diff --git a/RadixWallet/Features/TransactionReviewFeature/SubmitTransaction/SubmitTransaction.swift b/RadixWallet/Features/TransactionReviewFeature/SubmitTransaction/SubmitTransaction.swift index bac354fd2f..c9376437f4 100644 --- a/RadixWallet/Features/TransactionReviewFeature/SubmitTransaction/SubmitTransaction.swift +++ b/RadixWallet/Features/TransactionReviewFeature/SubmitTransaction/SubmitTransaction.swift @@ -106,7 +106,6 @@ public struct SubmitTransaction: Sendable, FeatureReducer { } return .send(.delegate(.manuallyDismiss)) - case .dismissTransactionAlert(.presented(.confirm)): return .concatenate(.cancel(id: CancellableId.transactionStatus), .send(.delegate(.manuallyDismiss))) case .dismissTransactionAlert(.presented(.cancel)): diff --git a/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift b/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift index 59f70bff53..418aca4bfd 100644 --- a/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift +++ b/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift @@ -61,6 +61,7 @@ extension TransactionReview { switch summary.detailedManifestClass { case nil: return nil + case .general, .transfer: if summary.detailedManifestClass == .general { guard !summary.deposits.isEmpty || !summary.withdrawals.isEmpty else { return nil } diff --git a/RadixWallet/RadixConnect/RadixConnect/RTC/SignalingClient/IncomingMessage/IncomingMessage+Decoding.swift b/RadixWallet/RadixConnect/RadixConnect/RTC/SignalingClient/IncomingMessage/IncomingMessage+Decoding.swift index 16f30eb68f..beb50a4243 100644 --- a/RadixWallet/RadixConnect/RadixConnect/RTC/SignalingClient/IncomingMessage/IncomingMessage+Decoding.swift +++ b/RadixWallet/RadixConnect/RadixConnect/RTC/SignalingClient/IncomingMessage/IncomingMessage+Decoding.swift @@ -49,7 +49,6 @@ extension SignalingClient.IncomingMessage: Decodable { let message = try container.decode(SignalingClient.ClientMessage.self, forKey: .message) let remoteClientId = try container.decode(RemoteClientID.self, forKey: .remoteClientId) self = .fromRemoteClient(.init(remoteClientId: remoteClientId, message: message)) - case .remoteClientJustConnected: let clientId = try container.decode(RemoteClientID.self, forKey: .remoteClientId) self = .fromSignalingServer(.notification(.remoteClientJustConnected(clientId))) @@ -59,7 +58,6 @@ extension SignalingClient.IncomingMessage: Decodable { case .remoteClientDisconnected: let clientId = try container.decode(RemoteClientID.self, forKey: .remoteClientId) self = .fromSignalingServer(.notification(.remoteClientDisconnected(clientId))) - case .missingRemoteClientError: let requestId = try container.decode(SignalingClient.ClientMessage.RequestID.self, forKey: .requestId) @@ -70,7 +68,6 @@ extension SignalingClient.IncomingMessage: Decodable { ) ) ) - case .validationError: let error = try container.decode(JSONValue.self, forKey: .error) let requestId = try container.decode(SignalingClient.ClientMessage.RequestID.self, forKey: .requestId)