From 7a8f89a22c1939e7c7866d744d075ddedb2294ac Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 11 Apr 2023 16:44:28 +0300 Subject: [PATCH 01/54] add wallet connect library --- Podfile | 1 + Podfile.lock | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Podfile b/Podfile index 4c2324f8e4..79a4144c88 100644 --- a/Podfile +++ b/Podfile @@ -23,6 +23,7 @@ abstract_target 'novawalletAll' do pod 'Starscream', :git => 'https://github.com/ERussel/Starscream.git', :tag => '4.0.5' pod 'CDMarkdownKit', :git => 'https://github.com/nova-wallet/CDMarkdownKit.git', :tag => '2.5.2' pod 'web3swift', :git => 'https://github.com/web3swift-team/web3swift.git', :tag => '3.0.6' + pod 'WalletConnectSwiftV2' target 'novawalletTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index c3a81fb219..a7de8dcec3 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -119,6 +119,25 @@ PODS: - BigInt (~> 5.0) - SwiftyBeaver (1.9.3) - TweetNacl (1.0.2) + - WalletConnectSwiftV2 (1.5.11): + - WalletConnectSwiftV2/WalletConnect (= 1.5.11) + - WalletConnectSwiftV2/Commons (1.5.11) + - WalletConnectSwiftV2/JSONRPC (1.5.11): + - WalletConnectSwiftV2/Commons + - WalletConnectSwiftV2/WalletConnect (1.5.11): + - WalletConnectSwiftV2/WalletConnectPairing + - WalletConnectSwiftV2/WalletConnectJWT (1.5.11): + - WalletConnectSwiftV2/WalletConnectKMS + - WalletConnectSwiftV2/WalletConnectKMS (1.5.11): + - WalletConnectSwiftV2/WalletConnectUtils + - WalletConnectSwiftV2/WalletConnectNetworking (1.5.11): + - WalletConnectSwiftV2/WalletConnectRelay + - WalletConnectSwiftV2/WalletConnectPairing (1.5.11): + - WalletConnectSwiftV2/WalletConnectNetworking + - WalletConnectSwiftV2/WalletConnectRelay (1.5.11): + - WalletConnectSwiftV2/WalletConnectJWT + - WalletConnectSwiftV2/WalletConnectUtils (1.5.11): + - WalletConnectSwiftV2/JSONRPC - Web3Core (3.0.6): - BigInt (~> 5.2.0) - CryptoSwift (~> 1.5.1) @@ -149,6 +168,7 @@ DEPENDENCIES: - SwiftLint - SwiftRLP (from `https://github.com/ERussel/SwiftRLP.git`) - SwiftyBeaver + - WalletConnectSwiftV2 - web3swift (from `https://github.com/web3swift-team/web3swift.git`, tag `3.0.6`) SPEC REPOS: @@ -175,6 +195,7 @@ SPEC REPOS: - SwiftLint - SwiftyBeaver - TweetNacl + - WalletConnectSwiftV2 - Web3Core - xxHash-Swift @@ -259,10 +280,11 @@ SPEC CHECKSUMS: SwiftRLP: f58417bfceecd45394fc619ccad14cf16e4ae6c1 SwiftyBeaver: 2e8acd6fc90c6d0a27055867a290794926d57c02 TweetNacl: 3abf4d1d2082b0114e7a67410e300892448951e6 + WalletConnectSwiftV2: 57aa9c4fef92428ac74d51d3d08fe5001af94827 Web3Core: 4a62a109cac056915d2d5023606438c89e229a1e web3swift: 944e76579b953a7b7e81dbb351c6dc0ed1defe63 xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 -PODFILE CHECKSUM: 0a13210996ecd62a01885637180607f5f7782656 +PODFILE CHECKSUM: 1be71b1dbd7a80dc24b945481a689219983455a2 COCOAPODS: 1.11.3 From cfe661a49fa02616c7cfa44ce9c582fd89c3b67c Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 12 Apr 2023 09:55:00 +0300 Subject: [PATCH 02/54] add wallet connect uri scan --- novawallet.xcodeproj/project.pbxproj | 76 +++++++++++++++---- .../Common/QRScanner/URI/URIQRMatcher.swift | 23 ++++++ .../QRScanner/URI/URIScanDelegate.swift | 5 ++ .../QRScanner/URI/URIScanPresenter.swift | 63 +++++++++++++++ .../QRScanner/URI/URIScanViewFactory.swift | 70 +++++++++++++++++ .../Modules/Settings/SettingsPresenter.swift | 2 + .../Modules/Settings/SettingsProtocols.swift | 1 + .../Modules/Settings/SettingsWireframe.swift | 9 +++ .../Settings/ViewModel/SettingsRow.swift | 5 ++ .../ViewModel/SettingsViewModelFactory.swift | 5 +- .../WalletConnectInteractor.swift | 7 ++ .../WalletConnectPresenter.swift | 35 +++++++++ .../WalletConnectProtocols.swift | 14 ++++ .../WalletConnectViewController.swift | 47 ++++++++++++ .../WalletConnectViewFactory.swift | 21 +++++ .../WalletConnectViewLayout.swift | 21 +++++ .../WalletConnectWireframe.swift | 11 +++ novawallet/en.lproj/Localizable.strings | 3 + novawallet/ru.lproj/Localizable.strings | 3 + 19 files changed, 404 insertions(+), 17 deletions(-) create mode 100644 novawallet/Common/QRScanner/URI/URIQRMatcher.swift create mode 100644 novawallet/Common/QRScanner/URI/URIScanDelegate.swift create mode 100644 novawallet/Common/QRScanner/URI/URIScanPresenter.swift create mode 100644 novawallet/Common/QRScanner/URI/URIScanViewFactory.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectInteractor.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectPresenter.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectProtocols.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectViewController.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift create mode 100644 novawallet/Modules/WalletConnect/WalletConnectWireframe.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index ec19d2e778..dc888e9680 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -152,6 +152,7 @@ 2CEFF4C2574F0AABE0E9BF89 /* ReferendumVoteSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A058D4A585F253CBF2968D /* ReferendumVoteSetupViewController.swift */; }; 2CF2F93AF862CF54FC46B560 /* PurchaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D44421CCD7AD220A05CD0E /* PurchaseInteractor.swift */; }; 2EC610DC06643A00876BED6E /* AssetListWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B249C5D43CB86CF62C165F8 /* AssetListWireframe.swift */; }; + 2EDE38E0F2E3494D16717A74 /* WalletConnectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDB8BE848C65B65FE4086D /* WalletConnectInteractor.swift */; }; 2F133E695C10ACC0D2FB44FE /* DAppSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0B1C175A2470AAA50DAC5 /* DAppSettingsViewController.swift */; }; 2F21134DE157A4B98ED309E2 /* AssetsSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611FBB25D55CF56F36026074 /* AssetsSearchViewController.swift */; }; 2F6FA089995FD12FB2AA814B /* ParitySignerWelcomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F45CB342D1F680257A548CF5 /* ParitySignerWelcomePresenter.swift */; }; @@ -258,6 +259,7 @@ 50758C9BBB27AE5732FF78BA /* StakingRewardPayoutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEBC03AB1841681427D38AF /* StakingRewardPayoutsViewController.swift */; }; 507AFE76D2D4EF2F739AE799 /* GovernanceDelegateInfoViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E4FF68D8A9AD54E4F089BC /* GovernanceDelegateInfoViewLayout.swift */; }; 5188FF070CD05F92C93A5055 /* CreateWatchOnlyProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537CCA1F2667A51731C56C88 /* CreateWatchOnlyProtocols.swift */; }; + 51FB88BCF778805D142DD8A9 /* WalletConnectProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */; }; 51FC48FA6FD4D2FB1781424D /* ReferralCrowdloanWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D51D60F19284936A6E9F47D /* ReferralCrowdloanWireframe.swift */; }; 52326E49B049C54434C95132 /* ParaStkCollatorsSearchWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C2612BBF75CF0FBD91764E /* ParaStkCollatorsSearchWireframe.swift */; }; 529B87AC9E500CC2A503A859 /* LedgerInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BF275FC06E8B06FA4D4719 /* LedgerInstructionsViewController.swift */; }; @@ -269,6 +271,7 @@ 544C8EB3D71227FAF2FD4658 /* GovernanceRemoveVotesConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29FE1BF422468BECDCDEE63 /* GovernanceRemoveVotesConfirmPresenter.swift */; }; 54D334605E9A7C71A4873CFC /* ParaStkRedeemWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578743E9101B334BFBE44CB6 /* ParaStkRedeemWireframe.swift */; }; 5510625BDA756B939ED7C586 /* AddDelegationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D44AF5C59681B54ECD7658 /* AddDelegationPresenter.swift */; }; + 5619C10BFFAEAF89883227B4 /* WalletConnectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */; }; 561F5B387B0A1682CE5DE7E4 /* ParitySignerTxScanViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEADC45D0C53E7A06A854DEB /* ParitySignerTxScanViewFactory.swift */; }; 5678BAE4B652C5C5E4284F28 /* AccountManagementViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18B94164B49123E62FA60B7 /* AccountManagementViewFactory.swift */; }; 575A729D07A6B984851E6DD0 /* DAppAuthConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F80D885306E1EFE4ABE321C /* DAppAuthConfirmPresenter.swift */; }; @@ -367,6 +370,7 @@ 75DAB313623E900EC475E215 /* LedgerTxConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCBCB7C3ABB6C06CD4681D44 /* LedgerTxConfirmViewFactory.swift */; }; 75E689BC8D16786DF2674171 /* AssetListViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F4883B898928C77D17C824 /* AssetListViewLayout.swift */; }; 766FE2FAB8509BF0F56EA3C0 /* ParaStkCollatorInfoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3B8502E5BF8CDD7ACE2DD0 /* ParaStkCollatorInfoProtocols.swift */; }; + 76769DE386BC2590D426A92A /* WalletConnectWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */; }; 76B0B7147181747A7CEDDDF6 /* GovernanceUnavailableTracksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E25CF67173500E0AC19387 /* GovernanceUnavailableTracksViewController.swift */; }; 76CF8508C6936FC9941F3C3E /* TokensManageAddProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C995D640129977CAB05982EC /* TokensManageAddProtocols.swift */; }; 78D94A761EFECED60F38232D /* CustomValidatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 270B309EC85D8897A4ADD98A /* CustomValidatorListViewController.swift */; }; @@ -401,6 +405,8 @@ 81ADC94E1CC47A2C6F0F1BEA /* GovernanceEditDelegationTracksProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32F1F0F6F985195CD19EDDB /* GovernanceEditDelegationTracksProtocols.swift */; }; 821518375113295E41E0481C /* ParitySignerTxQrViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B56BDC7E6221DE292498D3A /* ParitySignerTxQrViewFactory.swift */; }; 8217DCBEB74527D57AC82070 /* ParaStkStakeConfirmViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10E27EB6FF31F9D247DEFABB /* ParaStkStakeConfirmViewLayout.swift */; }; + 8268156233B4789FBD9114A5 /* WalletConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */; }; + 82AD4D0342FAF7C9EB4CF2AE /* WalletConnectViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */; }; 830A27C5447348F1D202D996 /* CrowdloanContributionSetupInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23E38DCBC74C528D7839B76 /* CrowdloanContributionSetupInteractor.swift */; }; 8321399396FA5B25BC93A090 /* DAppPhishingViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22914482F786318D8F6C5E27 /* DAppPhishingViewLayout.swift */; }; 8329367F06C83BA7A0B12A34 /* CommonDelegationTracksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7235097E09C94005B091B4 /* CommonDelegationTracksPresenter.swift */; }; @@ -1580,6 +1586,10 @@ 848FFE9025E6CF4300652AA5 /* StorageKeyEncodingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848FFE8F25E6CF4300652AA5 /* StorageKeyEncodingOperation.swift */; }; 848FFE9525E6DF2200652AA5 /* PagedKeysRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848FFE9425E6DF2200652AA5 /* PagedKeysRequest.swift */; }; 848FFE9A25E6E0E400652AA5 /* ValidatorExposure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848FFE9925E6E0E400652AA5 /* ValidatorExposure.swift */; }; + 8490110B29E5A491005D688B /* URIScanViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490110A29E5A491005D688B /* URIScanViewFactory.swift */; }; + 8490110D29E5A4BE005D688B /* URIScanDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490110C29E5A4BE005D688B /* URIScanDelegate.swift */; }; + 8490110F29E5A4F4005D688B /* URIQRMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490110E29E5A4F4005D688B /* URIQRMatcher.swift */; }; + 8490111129E5A509005D688B /* URIScanPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490111029E5A509005D688B /* URIScanPresenter.swift */; }; 849013AC24A80984008F705E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849013AB24A80984008F705E /* AppDelegate.swift */; }; 849013B524A80986008F705E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849013B424A80986008F705E /* Assets.xcassets */; }; 849013B824A80986008F705E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 849013B624A80986008F705E /* LaunchScreen.storyboard */; }; @@ -3181,6 +3191,7 @@ E9B2CD5127B700881A00EC1D /* TokensAddSelectNetworkInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA518E1D79D86360F145B428 /* TokensAddSelectNetworkInteractor.swift */; }; EAAB9E53189BC6394C5900D2 /* GovernanceSelectTracksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6962C8E51EB317DE3AAE4BDF /* GovernanceSelectTracksViewController.swift */; }; EAAFB082E2BB0CA418714061 /* ReferendumFullDetailsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CDCB8CF3D8EBE5DA7A5A30 /* ReferendumFullDetailsViewLayout.swift */; }; + EB11BF594D7E16A8885D47DD /* WalletConnectViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */; }; EB20C6B406155664B981BA94 /* GovernanceYourDelegationsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46DF4E6D8DAF6913474DED5 /* GovernanceYourDelegationsInteractor.swift */; }; EB376E61CD1C39AC148DE80C /* NftListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A60B27D3A045E0DEF23775 /* NftListViewController.swift */; }; EB5F587A71CCE1F0F86154CF /* ControllerAccountViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002A29AE58EB53E915330490 /* ControllerAccountViewFactory.swift */; }; @@ -3670,6 +3681,7 @@ 5AFA57BC935878DEBBE32EEA /* GovernanceRevokeDelegationConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceRevokeDelegationConfirmInteractor.swift; sourceTree = ""; }; 5B8B0940B2CB25AD9C36206E /* SelectValidatorsConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsConfirmViewController.swift; sourceTree = ""; }; 5BA3C305F49A4E609C7A5C14 /* GovernanceDelegateConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateConfirmViewController.swift; sourceTree = ""; }; + 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectPresenter.swift; sourceTree = ""; }; 5BCDAB970C17F9798AC79B08 /* DAppTxDetailsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppTxDetailsViewController.swift; sourceTree = ""; }; 5BE992187F6F3899568F3DAE /* TokensManageAddInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensManageAddInteractor.swift; sourceTree = ""; }; 5C4D750179BD034AC7300F38 /* TokensManageAddViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensManageAddViewFactory.swift; sourceTree = ""; }; @@ -3679,6 +3691,7 @@ 5CD1EE26234E390E938D9311 /* CommonDelegationTracksViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CommonDelegationTracksViewFactory.swift; sourceTree = ""; }; 5CD36AD8C414F8973CDA8A0F /* ParaStkUnstakeConfirmViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkUnstakeConfirmViewLayout.swift; sourceTree = ""; }; 5D0E02AA5D3EBA9B94950241 /* SelectValidatorsConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsConfirmWireframe.swift; sourceTree = ""; }; + 5DDDB8BE848C65B65FE4086D /* WalletConnectInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectInteractor.swift; sourceTree = ""; }; 5E2EB9EE4A87BD4A74040784 /* ReferendumDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumDetailsWireframe.swift; sourceTree = ""; }; 5E86B1DF9D9E8E5DD8DC49DE /* DelegateVotedReferendaViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegateVotedReferendaViewLayout.swift; sourceTree = ""; }; 5EAF3AEE27F7901458B39A7A /* Pods-novawalletAll-novawalletIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-novawalletAll-novawalletIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-novawalletAll-novawalletIntegrationTests/Pods-novawalletAll-novawalletIntegrationTests.debug.xcconfig"; sourceTree = ""; }; @@ -3686,12 +3699,14 @@ 5F791FE1B479CE1DF936F79F /* CrowdloanContributionConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanContributionConfirmViewFactory.swift; sourceTree = ""; }; 5FCECA20E6DCA5D228F44477 /* StakingUnbondSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingUnbondSetupPresenter.swift; sourceTree = ""; }; 5FD612D8F897463726CDD033 /* LedgerPerformOperationViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerPerformOperationViewController.swift; sourceTree = ""; }; + 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectProtocols.swift; sourceTree = ""; }; 611AD2A7BEEEBA634F56163D /* ParaStkUnstakeViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkUnstakeViewLayout.swift; sourceTree = ""; }; 611FBB25D55CF56F36026074 /* AssetsSearchViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetsSearchViewController.swift; sourceTree = ""; }; 61EBE466BDCF77E65FDCDF81 /* ExportMnemonicPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicPresenter.swift; sourceTree = ""; }; 6216F6F1B91F798F07695FB6 /* StakingAmountWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingAmountWireframe.swift; sourceTree = ""; }; 627CE71CD8C7CE59B8AFBAD6 /* CommonDelegationTracksProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CommonDelegationTracksProtocols.swift; sourceTree = ""; }; 62C5AF7E89A8C6CFF5AE03B1 /* YourWalletsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourWalletsProtocols.swift; sourceTree = ""; }; + 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewLayout.swift; sourceTree = ""; }; 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportSeedViewFactory.swift; sourceTree = ""; }; 638A65DAC86BAF9EB4D2F2F8 /* StakingRewardDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardDetailsWireframe.swift; sourceTree = ""; }; 6475F9C6C6B095B9C5026CE9 /* ParaStkYieldBoostStartViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYieldBoostStartViewController.swift; sourceTree = ""; }; @@ -3749,6 +3764,7 @@ 793046EA14E4CAB096803BCD /* NftDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftDetailsWireframe.swift; sourceTree = ""; }; 79BC90C31B17F8935FE8456F /* Pods-novawalletAll-novawallet.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-novawalletAll-novawallet.dev.xcconfig"; path = "Target Support Files/Pods-novawalletAll-novawallet/Pods-novawalletAll-novawallet.dev.xcconfig"; sourceTree = ""; }; 7A092ADC09DA0429548EBC08 /* NftListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftListPresenter.swift; sourceTree = ""; }; + 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewController.swift; sourceTree = ""; }; 7ACF32611D345B87BCE29FE0 /* DAppAddFavoriteWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAddFavoriteWireframe.swift; sourceTree = ""; }; 7B13D65B93E65B5112272962 /* DelegationReferendumVotersWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationReferendumVotersWireframe.swift; sourceTree = ""; }; 7B1A00299D9B50045E1A1983 /* DAppAddFavoriteProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAddFavoriteProtocols.swift; sourceTree = ""; }; @@ -4962,6 +4978,10 @@ 848FFE8F25E6CF4300652AA5 /* StorageKeyEncodingOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageKeyEncodingOperation.swift; sourceTree = ""; }; 848FFE9425E6DF2200652AA5 /* PagedKeysRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedKeysRequest.swift; sourceTree = ""; }; 848FFE9925E6E0E400652AA5 /* ValidatorExposure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatorExposure.swift; sourceTree = ""; }; + 8490110A29E5A491005D688B /* URIScanViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIScanViewFactory.swift; sourceTree = ""; }; + 8490110C29E5A4BE005D688B /* URIScanDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIScanDelegate.swift; sourceTree = ""; }; + 8490110E29E5A4F4005D688B /* URIQRMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQRMatcher.swift; sourceTree = ""; }; + 8490111029E5A509005D688B /* URIScanPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIScanPresenter.swift; sourceTree = ""; }; 849013A824A80984008F705E /* novawallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = novawallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 849013AB24A80984008F705E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 849013B424A80986008F705E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -6433,6 +6453,7 @@ B0B2C32E11E2F7F3D4A1D3AB /* ParaStkStakeSetupWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupWireframe.swift; sourceTree = ""; }; B14E9691A7628B66958F8744 /* LedgerInstructionsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsProtocols.swift; sourceTree = ""; }; B18E8361691E548ABAB33EA4 /* GovernanceEditDelegationTracksViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceEditDelegationTracksViewController.swift; sourceTree = ""; }; + B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewFactory.swift; sourceTree = ""; }; B1AC788C6E0A621B6D88D1BC /* ParaStkStakeSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupPresenter.swift; sourceTree = ""; }; B1F9B478321689D963F51C4E /* LedgerInstructionsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsViewFactory.swift; sourceTree = ""; }; B213C270130EDCF51303BFBE /* DAppAuthSettingsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAuthSettingsPresenter.swift; sourceTree = ""; }; @@ -6724,6 +6745,7 @@ F4FDA0FC26A57860003D753B /* EraCountdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraCountdown.swift; sourceTree = ""; }; F52B8815D6AF5E69B145D245 /* CustomValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CustomValidatorListViewFactory.swift; sourceTree = ""; }; F561E2D27FCEF7DEE3FE0B3D /* InAppUpdatesViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InAppUpdatesViewController.swift; sourceTree = ""; }; + F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectWireframe.swift; sourceTree = ""; }; F5E4CD58A9006CEB045E8977 /* ChangeWatchOnlyInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChangeWatchOnlyInteractor.swift; sourceTree = ""; }; F61D8973ADEB461DE2AD3E13 /* RecommendedValidatorListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewController.swift; sourceTree = ""; }; F63700316ADAC007DD318EC6 /* TransferSetupViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransferSetupViewLayout.swift; sourceTree = ""; }; @@ -6986,6 +7008,20 @@ path = DelegateVotedReferenda; sourceTree = ""; }; + 1398F9A5DE987487202A580E /* WalletConnect */ = { + isa = PBXGroup; + children = ( + 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */, + F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */, + 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */, + 5DDDB8BE848C65B65FE4086D /* WalletConnectInteractor.swift */, + 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */, + 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */, + B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */, + ); + path = WalletConnect; + sourceTree = ""; + }; 13A615962FF177D8E7483954 /* AdvancedWallet */ = { isa = PBXGroup; children = ( @@ -10238,6 +10274,7 @@ 8487582227F06AF300495306 /* QRScanner */ = { isa = PBXGroup; children = ( + 8490110729E5A453005D688B /* URI */, 847999B22888B46300D1BAD2 /* Address */, 8487584827F1830D00495306 /* QRImageUploadDelegate.swift */, 8487584427F096F800495306 /* QRScannerWireframe.swift */, @@ -10491,6 +10528,17 @@ path = EraValidatorsService; sourceTree = ""; }; + 8490110729E5A453005D688B /* URI */ = { + isa = PBXGroup; + children = ( + 8490110A29E5A491005D688B /* URIScanViewFactory.swift */, + 8490110C29E5A4BE005D688B /* URIScanDelegate.swift */, + 8490110E29E5A4F4005D688B /* URIQRMatcher.swift */, + 8490111029E5A509005D688B /* URIScanPresenter.swift */, + ); + path = URI; + sourceTree = ""; + }; 8490139F24A80984008F705E = { isa = PBXGroup; children = ( @@ -10633,6 +10681,7 @@ 018DE0E8A60963E2BDD94D13 /* YourWallets */, 83C426015DF863EBA46F1E3E /* Locks */, 9D97DD4BC9672502D2E2A625 /* TokensManage */, + 1398F9A5DE987487202A580E /* WalletConnect */, ); path = Modules; sourceTree = ""; @@ -12066,10 +12115,8 @@ 84B7C701289BFA79001A3566 /* AssetsManage */, 84B7C703289BFA79001A3566 /* WalletHistoryFilter */, 84B7C705289BFA79001A3566 /* AccountManagement */, - 84B7C707289BFA79001A3566 /* ParaStkStakeSetup */, 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, - 84B7C70C289BFA79001A3566 /* ParaStkUnstakeConfirm */, ); path = Modules; sourceTree = ""; @@ -12670,13 +12717,6 @@ path = AccountManagement; sourceTree = ""; }; - 84B7C707289BFA79001A3566 /* ParaStkStakeSetup */ = { - isa = PBXGroup; - children = ( - ); - path = ParaStkStakeSetup; - sourceTree = ""; - }; 84B7C708289BFA79001A3566 /* WalletList */ = { isa = PBXGroup; children = ( @@ -12693,13 +12733,6 @@ path = ControllerAccount; sourceTree = ""; }; - 84B7C70C289BFA79001A3566 /* ParaStkUnstakeConfirm */ = { - isa = PBXGroup; - children = ( - ); - path = ParaStkUnstakeConfirm; - sourceTree = ""; - }; 84BAD20E293A2CF200C55C49 /* View */ = { isa = PBXGroup; children = ( @@ -17097,6 +17130,7 @@ 840DC840288090030039A054 /* Bool+Int.swift in Sources */, 84BAD20B2939C45900C55C49 /* EtherscanKeys.swift in Sources */, 849ABE49262763BB00011A2A /* Longrun.swift in Sources */, + 8490111129E5A509005D688B /* URIScanPresenter.swift in Sources */, 84644A30256722D2004EAA4B /* TriangularedBlurButton+Inspectable.swift in Sources */, 844DBC60274D1B3E009F8351 /* IconWithTitleSubtitleViewModel.swift in Sources */, 849014B924AA87E3008F705E /* PinSetupViewController.swift in Sources */, @@ -18143,6 +18177,7 @@ 42B79A8D0D9540C1D97D991C /* AccountConfirmWireframe.swift in Sources */, 84DF21B12536DDC1005454AE /* TransferConfirmCommand.swift in Sources */, 887C44FD29AC7BCA00950F98 /* DelegateSingleVoteCollectionViewCell.swift in Sources */, + 8490110D29E5A4BE005D688B /* URIScanDelegate.swift in Sources */, AEA0C8BC2681140700F9666F /* YourValidatorList+CustomList.swift in Sources */, 88840D8C29DEDCD3002EFFFD /* KiltTransferAssetRecipientError.swift in Sources */, 84E83AA428632AF50000B418 /* XcmPalletTransfer.swift in Sources */, @@ -19155,6 +19190,7 @@ 148748ACAE23B7D15144015B /* DAppAuthSettingsViewFactory.swift in Sources */, B1F86CA723BB4D69C5EF989D /* ParaStkStakeSetupProtocols.swift in Sources */, 623474C49445578F030291B0 /* ParaStkStakeSetupWireframe.swift in Sources */, + 8490110B29E5A491005D688B /* URIScanViewFactory.swift in Sources */, 840B3D672899BFD200DA1DA9 /* QRScannerViewSettings.swift in Sources */, 577918C3D4AA22D887F605B5 /* ParaStkStakeSetupPresenter.swift in Sources */, 7050A26051FE62DB06B695F1 /* ParaStkStakeSetupInteractor.swift in Sources */, @@ -19248,6 +19284,7 @@ 58D5B4F17DA37C241FF96A5F /* ParaStkRebondViewController.swift in Sources */, F5F74CD4110DB94902AA836A /* ParaStkRebondViewLayout.swift in Sources */, C32B85D65D0290A577BFC85F /* ParaStkRebondViewFactory.swift in Sources */, + 8490110F29E5A4F4005D688B /* URIQRMatcher.swift in Sources */, 912ECC319A48CAD09FB694AC /* AssetsSearchProtocols.swift in Sources */, 8456D2C12993EE9F00D159A7 /* GovernanceDelegateStackCell.swift in Sources */, 8582395FEF296771447439FF /* AssetsSearchWireframe.swift in Sources */, @@ -19652,6 +19689,13 @@ 37CF597ACB2A32ABCEEFEE67 /* StakingRebagConfirmViewController.swift in Sources */, 09AB6DE2D19F1FA36BF08288 /* StakingRebagConfirmViewLayout.swift in Sources */, 4A24646D497B26E51926BA52 /* StakingRebagConfirmViewFactory.swift in Sources */, + 51FB88BCF778805D142DD8A9 /* WalletConnectProtocols.swift in Sources */, + 76769DE386BC2590D426A92A /* WalletConnectWireframe.swift in Sources */, + 5619C10BFFAEAF89883227B4 /* WalletConnectPresenter.swift in Sources */, + 2EDE38E0F2E3494D16717A74 /* WalletConnectInteractor.swift in Sources */, + 8268156233B4789FBD9114A5 /* WalletConnectViewController.swift in Sources */, + 82AD4D0342FAF7C9EB4CF2AE /* WalletConnectViewLayout.swift in Sources */, + EB11BF594D7E16A8885D47DD /* WalletConnectViewFactory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawallet/Common/QRScanner/URI/URIQRMatcher.swift b/novawallet/Common/QRScanner/URI/URIQRMatcher.swift new file mode 100644 index 0000000000..3bb4713159 --- /dev/null +++ b/novawallet/Common/QRScanner/URI/URIQRMatcher.swift @@ -0,0 +1,23 @@ +import Foundation +import CommonWallet +import SubstrateSdk + +protocol URIQRMatching { + func match(code: String) -> String? +} + +final class SchemeURIMatcher: URIQRMatching { + let scheme: String + + init(scheme: String) { + self.scheme = scheme + } + + func match(code: String) -> String? { + guard let url = URL(string: code), url.scheme == scheme else { + return nil + } + + return code + } +} diff --git a/novawallet/Common/QRScanner/URI/URIScanDelegate.swift b/novawallet/Common/QRScanner/URI/URIScanDelegate.swift new file mode 100644 index 0000000000..4b2b77740f --- /dev/null +++ b/novawallet/Common/QRScanner/URI/URIScanDelegate.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol URIScanDelegate: AnyObject { + func uriScanDidReceive(uri: String, context: AnyObject?) +} diff --git a/novawallet/Common/QRScanner/URI/URIScanPresenter.swift b/novawallet/Common/QRScanner/URI/URIScanPresenter.swift new file mode 100644 index 0000000000..4e1f5871b8 --- /dev/null +++ b/novawallet/Common/QRScanner/URI/URIScanPresenter.swift @@ -0,0 +1,63 @@ +import Foundation +import SoraFoundation + +final class URIScanPresenter: QRScannerPresenter { + let matcher: URIQRMatching + let context: AnyObject? + let qrExtractionError: LocalizableResource + + weak var delegate: URIScanDelegate? + + private var lastHandledCode: String? + + let localizationManager: LocalizationManagerProtocol + + init( + matcher: URIQRMatching, + wireframe: QRScannerWireframeProtocol, + delegate: URIScanDelegate, + context: AnyObject?, + qrScanService: QRCaptureServiceProtocol, + qrExtractionService: QRExtractionServiceProtocol, + localizationManager: LocalizationManagerProtocol, + qrExtractionError: LocalizableResource, + logger: LoggerProtocol? = nil + ) { + self.matcher = matcher + self.delegate = delegate + self.context = context + self.qrExtractionError = qrExtractionError + self.localizationManager = localizationManager + + super.init( + wireframe: wireframe, + qrScanService: qrScanService, + qrExtractionService: qrExtractionService, + logger: logger + ) + } + + private func handleFailure() { + let locale = localizationManager.selectedLocale + let message = qrExtractionError.value(for: locale) + view?.present(message: message, animated: true) + } + + override func handle(code: String) { + guard lastHandledCode != code else { + return + } + + lastHandledCode = code + + if let uri = matcher.match(code: code) { + DispatchQueue.main.async { [weak self] in + self?.delegate?.uriScanDidReceive(uri: uri, context: self?.context) + } + } else { + DispatchQueue.main.async { [weak self] in + self?.handleFailure() + } + } + } +} diff --git a/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift b/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift new file mode 100644 index 0000000000..8521c12fe3 --- /dev/null +++ b/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift @@ -0,0 +1,70 @@ +import Foundation +import SoraFoundation + +struct URIScanViewFactory { + static func createScan( + for delegate: URIScanDelegate, + context: AnyObject? + ) -> QRScannerViewProtocol? { + let title = LocalizableResource { locale in + R.string.localizable.commonWalletConnect(preferredLanguages: locale.rLanguages) + } + + let message = LocalizableResource { locale in + R.string.localizable.walletConnectScanMessage(preferredLanguages: locale.rLanguages) + } + + let qrExtractionError = LocalizableResource { locale in + R.string.localizable.walletConnectScanError(preferredLanguages: locale.rLanguages) + } + + return createView( + matcher: SchemeURIMatcher(scheme: "wc"), + title: title, + message: message, + qrExtractionError: qrExtractionError, + for: delegate, + context: context + ) + } + + static func createView( + matcher: URIQRMatching, + title: LocalizableResource, + message: LocalizableResource, + qrExtractionError: LocalizableResource, + for delegate: URIScanDelegate, + context: AnyObject? + ) -> QRScannerViewProtocol? { + let processingQueue = QRCaptureService.processingQueue + let qrService = QRCaptureService(delegate: nil, delegateQueue: processingQueue) + let qrExtractor = QRExtractionService(processingQueue: processingQueue) + + let wireframe = QRScannerWireframe() + + let localizationManager = LocalizationManager.shared + + let presenter = URIScanPresenter( + matcher: matcher, + wireframe: wireframe, + delegate: delegate, + context: context, + qrScanService: qrService, + qrExtractionService: qrExtractor, + localizationManager: localizationManager, + qrExtractionError: qrExtractionError, + logger: Logger.shared + ) + + let view = QRScannerViewController( + title: title, + message: message, + presenter: presenter, + localizationManager: localizationManager + ) + + presenter.view = view + + return view + } +} diff --git a/novawallet/Modules/Settings/SettingsPresenter.swift b/novawallet/Modules/Settings/SettingsPresenter.swift index f56268aaf1..ce41178b73 100644 --- a/novawallet/Modules/Settings/SettingsPresenter.swift +++ b/novawallet/Modules/Settings/SettingsPresenter.swift @@ -117,6 +117,8 @@ extension SettingsPresenter: SettingsPresenterProtocol { show(url: config.termsURL) case .privacyPolicy: show(url: config.privacyPolicyURL) + case .walletConnect: + wireframe.showWalletConnect(from: view) } } diff --git a/novawallet/Modules/Settings/SettingsProtocols.swift b/novawallet/Modules/Settings/SettingsProtocols.swift index 2b322765a8..a1a9f95686 100644 --- a/novawallet/Modules/Settings/SettingsProtocols.swift +++ b/novawallet/Modules/Settings/SettingsProtocols.swift @@ -42,4 +42,5 @@ protocol SettingsWireframeProtocol: ErrorPresentable, AlertPresentable, WebPrese func showPincodeChange(from view: ControllerBackedProtocol?) func showCurrencies(from view: ControllerBackedProtocol?) func show(url: URL, from view: ControllerBackedProtocol?) + func showWalletConnect(from view: ControllerBackedProtocol?) } diff --git a/novawallet/Modules/Settings/SettingsWireframe.swift b/novawallet/Modules/Settings/SettingsWireframe.swift index cd815d44fc..8841ce42f6 100644 --- a/novawallet/Modules/Settings/SettingsWireframe.swift +++ b/novawallet/Modules/Settings/SettingsWireframe.swift @@ -69,6 +69,15 @@ final class SettingsWireframe: SettingsWireframeProtocol, AuthorizationPresentab } } + func showWalletConnect(from view: ControllerBackedProtocol?) { + guard let walletConnectView = WalletConnectViewFactory.createView() else { + return + } + + walletConnectView.controller.hidesBottomBarWhenPushed = true + view?.controller.navigationController?.pushViewController(walletConnectView.controller, animated: true) + } + // MARK: Private private func showPinSetup(from view: ControllerBackedProtocol?) { diff --git a/novawallet/Modules/Settings/ViewModel/SettingsRow.swift b/novawallet/Modules/Settings/ViewModel/SettingsRow.swift index 6b95226db1..734b2c2133 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsRow.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsRow.swift @@ -15,6 +15,7 @@ enum SettingsRow { case github case terms case privacyPolicy + case walletConnect } extension SettingsRow { @@ -47,6 +48,8 @@ extension SettingsRow { return R.string.localizable.aboutTerms(preferredLanguages: locale.rLanguages) case .privacyPolicy: return R.string.localizable.aboutPrivacy(preferredLanguages: locale.rLanguages) + case .walletConnect: + return R.string.localizable.commonWalletConnect(preferredLanguages: locale.rLanguages) } } @@ -78,6 +81,8 @@ extension SettingsRow { return R.image.iconTerms()! case .privacyPolicy: return R.image.iconTerms()! + case .walletConnect: + return R.image.iconWallets() } } } diff --git a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift index 64e5c55e69..0624c1751d 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift @@ -31,7 +31,10 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { locale: Locale ) -> [(SettingsSection, [SettingsCellViewModel])] { [ - (.general, [createCommonViewViewModel(row: .wallets, locale: locale)]), + (.general, [ + createCommonViewViewModel(row: .wallets, locale: locale), + createCommonViewViewModel(row: .walletConnect, locale: locale) + ]), (.preferences, [ createValuableViewModel(row: .currency, value: currency, locale: locale), createLanguageViewModel(from: language, locale: locale) diff --git a/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift new file mode 100644 index 0000000000..1bc101b434 --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift @@ -0,0 +1,7 @@ +import UIKit + +final class WalletConnectInteractor { + weak var presenter: WalletConnectInteractorOutputProtocol? +} + +extension WalletConnectInteractor: WalletConnectInteractorInputProtocol {} diff --git a/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift b/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift new file mode 100644 index 0000000000..2a64d9f02e --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift @@ -0,0 +1,35 @@ +import Foundation + +final class WalletConnectPresenter { + weak var view: WalletConnectViewProtocol? + let wireframe: WalletConnectWireframeProtocol + let interactor: WalletConnectInteractorInputProtocol + + let logger: LoggerProtocol + + init( + interactor: WalletConnectInteractorInputProtocol, + wireframe: WalletConnectWireframeProtocol, + logger: LoggerProtocol + ) { + self.interactor = interactor + self.wireframe = wireframe + self.logger = logger + } +} + +extension WalletConnectPresenter: WalletConnectPresenterProtocol { + func setup() {} + + func showScan() { + wireframe.showScan(from: view, delegate: self) + } +} + +extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol {} + +extension WalletConnectPresenter: URIScanDelegate { + func uriScanDidReceive(uri: String, context _: AnyObject?) { + logger.debug("Wallet Connect URI: \(uri)") + } +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift b/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift new file mode 100644 index 0000000000..4b49ddd38e --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift @@ -0,0 +1,14 @@ +protocol WalletConnectViewProtocol: ControllerBackedProtocol {} + +protocol WalletConnectPresenterProtocol: AnyObject { + func setup() + func showScan() +} + +protocol WalletConnectInteractorInputProtocol: AnyObject {} + +protocol WalletConnectInteractorOutputProtocol: AnyObject {} + +protocol WalletConnectWireframeProtocol: AnyObject { + func showScan(from view: WalletConnectViewProtocol?, delegate: URIScanDelegate) +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewController.swift b/novawallet/Modules/WalletConnect/WalletConnectViewController.swift new file mode 100644 index 0000000000..cd2c6a6866 --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectViewController.swift @@ -0,0 +1,47 @@ +import UIKit + +final class WalletConnectViewController: UIViewController, ViewHolder { + typealias RootViewType = WalletConnectViewLayout + + let presenter: WalletConnectPresenterProtocol + + init(presenter: WalletConnectPresenterProtocol) { + self.presenter = presenter + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = WalletConnectViewLayout() + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupScanItem() + setupLocalization() + + presenter.setup() + } + + private func setupScanItem() { + navigationItem.rightBarButtonItem = rootView.scanItem + + rootView.scanItem.target = self + rootView.scanItem.action = #selector(actionScan) + } + + private func setupLocalization() { + title = "Your sessions" + } + + @objc func actionScan() { + presenter.showScan() + } +} + +extension WalletConnectViewController: WalletConnectViewProtocol {} diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift b/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift new file mode 100644 index 0000000000..6368896dbb --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift @@ -0,0 +1,21 @@ +import Foundation + +struct WalletConnectViewFactory { + static func createView() -> WalletConnectViewProtocol? { + let interactor = WalletConnectInteractor() + let wireframe = WalletConnectWireframe() + + let presenter = WalletConnectPresenter( + interactor: interactor, + wireframe: wireframe, + logger: Logger.shared + ) + + let view = WalletConnectViewController(presenter: presenter) + + presenter.view = view + interactor.presenter = presenter + + return view + } +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift b/novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift new file mode 100644 index 0000000000..76a0e941bd --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift @@ -0,0 +1,21 @@ +import UIKit + +final class WalletConnectViewLayout: UIView { + let scanItem = UIBarButtonItem( + image: R.image.iconScanQr()!, + style: .plain, + target: nil, + action: nil + ) + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = R.color.colorSecondaryScreenBackground()! + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectWireframe.swift b/novawallet/Modules/WalletConnect/WalletConnectWireframe.swift new file mode 100644 index 0000000000..d9914e14e9 --- /dev/null +++ b/novawallet/Modules/WalletConnect/WalletConnectWireframe.swift @@ -0,0 +1,11 @@ +import Foundation + +final class WalletConnectWireframe: WalletConnectWireframeProtocol { + func showScan(from view: WalletConnectViewProtocol?, delegate: URIScanDelegate) { + guard let scanView = URIScanViewFactory.createScan(for: delegate, context: nil) else { + return + } + + view?.controller.present(scanView.controller, animated: true) + } +} diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index a5734da774..4bbfc3375c 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1211,3 +1211,6 @@ "evm.contract.call" = "Contract call"; "evm.contract" = "Contract"; "evm.contract.function" = "Function"; +"common.wallet.connect" = "Wallet Connect"; +"wallet.connect.scan.message" = "Scan QR from DApp"; +"wallet.connect.scan.error" = "Invalid URI format"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 3ee7a61f7e..fbcdbef8a2 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1211,3 +1211,6 @@ "evm.contract.call" = "Вызов контракта"; "evm.contract" = "Контракт"; "evm.contract.function" = "Функция"; +"common.wallet.connect" = "Wallet Connect"; +"wallet.connect.scan.message" = "Отсканируйте QR код"; +"wallet.connect.scan.error" = "Неверный формат URI"; From 5e933569b741ffb548159a3a9fa97fbf043303aa Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 13 Apr 2023 16:23:25 +0300 Subject: [PATCH 03/54] refactoring --- novawallet.xcodeproj/project.pbxproj | 16 ++ .../Common/Configs/ApplicationConfigs.swift | 7 + .../WalletConnect/WalletConnectMetadata.swift | 21 ++ .../WalletConnect/WalletConnectService.swift | 234 ++++++++++++++++++ .../WalletConnectInteractor.swift | 42 +++- .../WalletConnectPresenter.swift | 6 +- .../WalletConnectProtocols.swift | 5 +- .../WalletConnectViewFactory.swift | 9 +- 8 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift create mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectService.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index dc888e9680..6d5e431f31 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -916,6 +916,7 @@ 844138EC2830106C00AFEF6D /* ParaStkNetworkInfoViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844138EB2830106B00AFEF6D /* ParaStkNetworkInfoViewModelFactory.swift */; }; 844138EE28303A2E00AFEF6D /* ParaStkStateViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844138ED28303A2E00AFEF6D /* ParaStkStateViewModelFactory.swift */; }; 84415BCA26E783EB005A3683 /* PayoutValidatorsForNominatorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84415BC926E783EB005A3683 /* PayoutValidatorsForNominatorFactory.swift */; }; + 8441D3C929E8015100070BA3 /* WalletConnectMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */; }; 8442002028E6FDBE00C49C4A /* CrowdloanListViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8442001F28E6FDBE00C49C4A /* CrowdloanListViewManager.swift */; }; 8442002328E6FE1E00C49C4A /* ReferendumsViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8442002228E6FE1E00C49C4A /* ReferendumsViewManager.swift */; }; 8442002528E6FEEE00C49C4A /* ReferendumsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8442002428E6FEEE00C49C4A /* ReferendumsProtocols.swift */; }; @@ -1590,6 +1591,7 @@ 8490110D29E5A4BE005D688B /* URIScanDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490110C29E5A4BE005D688B /* URIScanDelegate.swift */; }; 8490110F29E5A4F4005D688B /* URIQRMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490110E29E5A4F4005D688B /* URIQRMatcher.swift */; }; 8490111129E5A509005D688B /* URIScanPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490111029E5A509005D688B /* URIScanPresenter.swift */; }; + 8490111429E68FCB005D688B /* WalletConnectService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8490111329E68FCB005D688B /* WalletConnectService.swift */; }; 849013AC24A80984008F705E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849013AB24A80984008F705E /* AppDelegate.swift */; }; 849013B524A80986008F705E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849013B424A80986008F705E /* Assets.xcassets */; }; 849013B824A80986008F705E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 849013B624A80986008F705E /* LaunchScreen.storyboard */; }; @@ -4301,6 +4303,7 @@ 844138EB2830106B00AFEF6D /* ParaStkNetworkInfoViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParaStkNetworkInfoViewModelFactory.swift; sourceTree = ""; }; 844138ED28303A2E00AFEF6D /* ParaStkStateViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParaStkStateViewModelFactory.swift; sourceTree = ""; }; 84415BC926E783EB005A3683 /* PayoutValidatorsForNominatorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayoutValidatorsForNominatorFactory.swift; sourceTree = ""; }; + 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectMetadata.swift; sourceTree = ""; }; 8442001F28E6FDBE00C49C4A /* CrowdloanListViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanListViewManager.swift; sourceTree = ""; }; 8442002228E6FE1E00C49C4A /* ReferendumsViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsViewManager.swift; sourceTree = ""; }; 8442002428E6FEEE00C49C4A /* ReferendumsProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsProtocols.swift; sourceTree = ""; }; @@ -4982,6 +4985,7 @@ 8490110C29E5A4BE005D688B /* URIScanDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIScanDelegate.swift; sourceTree = ""; }; 8490110E29E5A4F4005D688B /* URIQRMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQRMatcher.swift; sourceTree = ""; }; 8490111029E5A509005D688B /* URIScanPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIScanPresenter.swift; sourceTree = ""; }; + 8490111329E68FCB005D688B /* WalletConnectService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectService.swift; sourceTree = ""; }; 849013A824A80984008F705E /* novawallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = novawallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 849013AB24A80984008F705E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 849013B424A80986008F705E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -7970,6 +7974,7 @@ 84155DE8253980D700A27058 /* Services */ = { isa = PBXGroup; children = ( + 8490111229E68FAD005D688B /* WalletConnect */, 8863C7AA29D499BC0068AD54 /* Web3Name */, 880855EE28D099BD004255E7 /* CrowdloanService */, 8411707D285B15C8006F4DFB /* XcmService */, @@ -10539,6 +10544,15 @@ path = URI; sourceTree = ""; }; + 8490111229E68FAD005D688B /* WalletConnect */ = { + isa = PBXGroup; + children = ( + 8490111329E68FCB005D688B /* WalletConnectService.swift */, + 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */, + ); + path = WalletConnect; + sourceTree = ""; + }; 8490139F24A80984008F705E = { isa = PBXGroup; children = ( @@ -16616,6 +16630,7 @@ F458D3C12642B7D80055CB75 /* LearnMoreView.swift in Sources */, F4E17FCA272182A200FE36D3 /* MoonbeamAgreeRemarkResponse.swift in Sources */, 84735E782881986900BADC1B /* AssetListBasePresenter+Model.swift in Sources */, + 8441D3C929E8015100070BA3 /* WalletConnectMetadata.swift in Sources */, 8434B604298D474D00FACF4C /* GovernanceAddDelegationTracksPresenter.swift in Sources */, 8460E11F25420A2C00826F55 /* DetailsTriangularedView+Inspectable.swift in Sources */, AE8D028E2638074D00780D18 /* CIKeys.generated.swift in Sources */, @@ -19396,6 +19411,7 @@ 843125D1299BBC5F0063745B /* GovernanceRevokeDelegationInteractorError.swift in Sources */, 30542C0BD486FD1583F36BA2 /* LedgerNetworkSelectionProtocols.swift in Sources */, 84F1D66D29051F240050F4E3 /* ReferendumReuseLockModel.swift in Sources */, + 8490111429E68FCB005D688B /* WalletConnectService.swift in Sources */, 84FBDBE128C884DA00CC1037 /* ParaStkYieldBoostStorageSubscriber.swift in Sources */, 845B07F929162D24005785D3 /* Gov1LocalMappingFactory.swift in Sources */, C102544345E604976BF7AFFC /* LedgerNetworkSelectionWireframe.swift in Sources */, diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index e3f0b751ea..419af61d53 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -35,6 +35,7 @@ protocol ApplicationConfigProtocol { var inAppUpdatesEntrypointURL: URL { get } var inAppUpdatesChangelogsURL: URL { get } var slip44URL: URL { get } + var walletConnectProjectId: String { get } } final class ApplicationConfig { @@ -228,5 +229,11 @@ extension ApplicationConfig: ApplicationConfigProtocol { var slip44URL: URL { URL(string: "https://raw.githubusercontent.com/nova-wallet/nova-utils/master/assets/slip44.json")! } + // swiftlint:enable line_length + + // TODO: Consider to make secret + var walletConnectProjectId: String { + "f28de7baff5ecce73ed0c9f9f97dbd30" + } } diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift b/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift new file mode 100644 index 0000000000..1a06203d9e --- /dev/null +++ b/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift @@ -0,0 +1,21 @@ +import Foundation + +struct WalletConnectMetadata { + let projectId: String + let name: String + let description: String + let website: String + let icon: String +} + +extension WalletConnectMetadata { + static func nova(with projectId: String) -> WalletConnectMetadata { + .init( + projectId: projectId, + name: "Nova wallet", + description: "Non-custodial Polkadot & Kusama wallet", + website: "https://novawallet.io", + icon: "https://github.com/nova-wallet/branding/raw/master/logos/Nova_Wallet_Horizontal_On_White_200px.png" + ) + } +} diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift new file mode 100644 index 0000000000..3f31791a05 --- /dev/null +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -0,0 +1,234 @@ +import Foundation +import WalletConnectSwiftV2 +import Starscream +import Combine + +protocol WalletConnectServiceDelegate: AnyObject { + func walletConnect(service: WalletConnectServiceProtocol, proposal: Session.Proposal) + func walletConnect(service: WalletConnectServiceProtocol, establishedSession: Session) + func walletConnect(service: WalletConnectServiceProtocol, request: Request) + func walletConnect(service: WalletConnectServiceProtocol, error: WalletConnectServiceError) +} + +protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { + var delegate: WalletConnectServiceDelegate? { get set } + + func connect(uri: String) +} + +enum WalletConnectServiceError: Error { + case setupNeeded + case connectFailed(uri: String, internalError: Error) +} + +final class WalletConnectService { + private var networking: NetworkingInteractor? + @Atomic(defaultValue: nil) private var pairing: PairingClient? + private var client: SignClient? + + @Atomic(defaultValue: nil) private var proposalCancellable: AnyCancellable? + @Atomic(defaultValue: nil) private var sessionCancellable: AnyCancellable? + @Atomic(defaultValue: nil) private var requestCancellable: AnyCancellable? + + weak var delegate: WalletConnectServiceDelegate? + + let relayHost: String + let logger: LoggerProtocol + let metadata: WalletConnectMetadata + + init( + metadata: WalletConnectMetadata, + relayHost: String = "relay.walletconnect.com", + logger: LoggerProtocol = Logger.shared + ) { + self.metadata = metadata + self.relayHost = relayHost + self.logger = logger + } + + private func setupSubscription() { + proposalCancellable = client?.sessionProposalPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] proposal in + guard let strongSelf = self else { + return + } + + strongSelf.delegate?.walletConnect(service: strongSelf, proposal: proposal) + } + + sessionCancellable = client?.sessionSettlePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] session in + guard let strongSelf = self else { + return + } + + strongSelf.delegate?.walletConnect(service: strongSelf, establishedSession: session) + } + + requestCancellable = client?.sessionRequestPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] request in + guard let strongSelf = self else { + return + } + + strongSelf.delegate?.walletConnect(service: strongSelf, request: request) + } + } + + private func clearSubscriptions() { + proposalCancellable?.cancel() + proposalCancellable = nil + + sessionCancellable?.cancel() + sessionCancellable = nil + + requestCancellable?.cancel() + requestCancellable = nil + } + + private func setupClient() { + guard client == nil, let pairing = pairing, let networking = networking else { + return + } + + let metadata = AppMetadata( + name: metadata.name, + description: metadata.description, + url: metadata.website, + icons: [metadata.icon] + ) + + client = SignClientFactory.create( + metadata: metadata, + pairingClient: pairing, + networkingClient: networking + ) + } + + private func clearClient() { + client = nil + } + + private func setupPairing() { + guard pairing == nil, let networking = networking else { + return + } + + pairing = PairingClientFactory.create(networkingClient: networking) + } + + private func clearPairing() { + pairing = nil + } + + private func setupNetworking() { + guard networking == nil else { + return + } + + let socketFactory = DefaultSocketFactory() + let relayClient = RelayClient(relayHost: relayHost, projectId: metadata.projectId, socketFactory: socketFactory) + networking = NetworkingClientFactory.create(relayClient: relayClient) + } + + private func clearNetworking() { + do { + try networking?.disconnect(closeCode: .normalClosure) + } catch { + logger.error("Disconnection failed: \(error)") + } + + networking = nil + } + + private func notify(error: WalletConnectServiceError) { + DispatchQueue.main.async { + self.delegate?.walletConnect(service: self, error: error) + } + } +} + +extension WalletConnectService: WalletConnectServiceProtocol { + func connect(uri: String) { + guard let pairingUri = WalletConnectURI(string: uri), let pairing = pairing else { + notify(error: .setupNeeded) + return + } + + Task { [weak self] in + do { + try await pairing.pair(uri: pairingUri) + self?.logger.debug("Pairing submitted: \(uri)") + } catch { + self?.logger.error("Pairing failed \(uri): \(error)") + self?.notify(error: .connectFailed(uri: uri, internalError: error)) + } + } + } + + func setup() { + setupNetworking() + setupPairing() + setupClient() + setupSubscription() + } + + func throttle() { + clearNetworking() + clearPairing() + clearClient() + clearSubscriptions() + } +} + +private final class DefaultWebSocket: WebSocket, WebSocketConnecting { + public var isConnected: Bool { + connected + } + + @Atomic(defaultValue: false) private var connected: Bool + + public var onConnect: (() -> Void)? + + public var onDisconnect: ((Error?) -> Void)? + + public var onText: ((String) -> Void)? + + override init(request: URLRequest, engine: Engine) { + super.init(request: request, engine: engine) + + onEvent = { [weak self] event in + switch event { + case .connected: + self?.connected = true + self?.onConnect?() + case let .disconnected(message, code): + self?.connected = false + self?.onDisconnect?(WSError(type: .protocolError, message: message, code: code)) + case .cancelled, .reconnectSuggested: + self?.connected = false + self?.onDisconnect?(nil) + case let .error(error): + self?.connected = false + self?.onDisconnect?(error) + case let .text(text): + self?.onText?(text) + default: + break + } + } + } +} + +private struct DefaultSocketFactory: WebSocketFactory { + func create(with url: URL) -> WebSocketConnecting { + var urlRequest = URLRequest(url: url) + + // This is specifics of Starscream due to how Origin is set + urlRequest.addValue("allowed.domain.com", forHTTPHeaderField: "Origin") + return DefaultWebSocket(request: urlRequest) + } +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift index 1bc101b434..ae12147f49 100644 --- a/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift +++ b/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift @@ -1,7 +1,47 @@ import UIKit +import WalletConnectSwiftV2 final class WalletConnectInteractor { weak var presenter: WalletConnectInteractorOutputProtocol? + + let service: WalletConnectServiceProtocol + let logger: LoggerProtocol + + init(service: WalletConnectServiceProtocol, logger: LoggerProtocol) { + self.service = service + self.logger = logger + } + + deinit { + service.throttle() + } +} + +extension WalletConnectInteractor: WalletConnectInteractorInputProtocol { + func setup() { + service.delegate = self + service.setup() + } + + func connect(uri: String) { + service.connect(uri: uri) + } } -extension WalletConnectInteractor: WalletConnectInteractorInputProtocol {} +extension WalletConnectInteractor: WalletConnectServiceDelegate { + func walletConnect(service _: WalletConnectServiceProtocol, proposal: Session.Proposal) { + logger.debug("Proposal: \(proposal)") + } + + func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { + logger.debug("New session: \(establishedSession)") + } + + func walletConnect(service _: WalletConnectServiceProtocol, request: Request) { + logger.debug("New session: \(request)") + } + + func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { + logger.debug("Error: \(error)") + } +} diff --git a/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift b/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift index 2a64d9f02e..65027c72f7 100644 --- a/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift +++ b/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift @@ -19,7 +19,9 @@ final class WalletConnectPresenter { } extension WalletConnectPresenter: WalletConnectPresenterProtocol { - func setup() {} + func setup() { + interactor.setup() + } func showScan() { wireframe.showScan(from: view, delegate: self) @@ -31,5 +33,7 @@ extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol {} extension WalletConnectPresenter: URIScanDelegate { func uriScanDidReceive(uri: String, context _: AnyObject?) { logger.debug("Wallet Connect URI: \(uri)") + + interactor.connect(uri: uri) } } diff --git a/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift b/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift index 4b49ddd38e..96798510bc 100644 --- a/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift +++ b/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift @@ -5,7 +5,10 @@ protocol WalletConnectPresenterProtocol: AnyObject { func showScan() } -protocol WalletConnectInteractorInputProtocol: AnyObject {} +protocol WalletConnectInteractorInputProtocol: AnyObject { + func setup() + func connect(uri: String) +} protocol WalletConnectInteractorOutputProtocol: AnyObject {} diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift b/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift index 6368896dbb..12e80bf23d 100644 --- a/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift +++ b/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift @@ -2,7 +2,7 @@ import Foundation struct WalletConnectViewFactory { static func createView() -> WalletConnectViewProtocol? { - let interactor = WalletConnectInteractor() + let interactor = createInteractor() let wireframe = WalletConnectWireframe() let presenter = WalletConnectPresenter( @@ -18,4 +18,11 @@ struct WalletConnectViewFactory { return view } + + private static func createInteractor() -> WalletConnectInteractor { + let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) + let service = WalletConnectService(metadata: metadata) + + return .init(service: service, logger: Logger.shared) + } } From 00046efcedd6ad4496399cfbcd5dfa10f4533f37 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 24 Apr 2023 17:54:05 +0500 Subject: [PATCH 04/54] fix tests --- novawalletTests/Mocks/ModuleMocks.swift | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/novawalletTests/Mocks/ModuleMocks.swift b/novawalletTests/Mocks/ModuleMocks.swift index e17ee3628f..0d55a8380f 100644 --- a/novawalletTests/Mocks/ModuleMocks.swift +++ b/novawalletTests/Mocks/ModuleMocks.swift @@ -20036,6 +20036,21 @@ import UIKit.UIImage + func showWalletConnect(from view: ControllerBackedProtocol?) { + + return cuckoo_manager.call("showWalletConnect(from: ControllerBackedProtocol?)", + parameters: (view), + escapingParameters: (view), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.showWalletConnect(from: view)) + + } + + + func present(message: String?, title: String?, closeAction: String?, from view: ControllerBackedProtocol?) { return cuckoo_manager.call("present(message: String?, title: String?, closeAction: String?, from: ControllerBackedProtocol?)", @@ -20133,6 +20148,11 @@ import UIKit.UIImage return .init(stub: cuckoo_manager.createStub(for: MockSettingsWireframeProtocol.self, method: "show(url: URL, from: ControllerBackedProtocol?)", parameterMatchers: matchers)) } + func showWalletConnect(from view: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(ControllerBackedProtocol?)> where M1.OptionalMatchedType == ControllerBackedProtocol { + let matchers: [Cuckoo.ParameterMatcher<(ControllerBackedProtocol?)>] = [wrap(matchable: view) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockSettingsWireframeProtocol.self, method: "showWalletConnect(from: ControllerBackedProtocol?)", parameterMatchers: matchers)) + } + func present(message: M1, title: M2, closeAction: M3, from view: M4) -> Cuckoo.ProtocolStubNoReturnFunction<(String?, String?, String?, ControllerBackedProtocol?)> where M1.OptionalMatchedType == String, M2.OptionalMatchedType == String, M3.OptionalMatchedType == String, M4.OptionalMatchedType == ControllerBackedProtocol { let matchers: [Cuckoo.ParameterMatcher<(String?, String?, String?, ControllerBackedProtocol?)>] = [wrap(matchable: message) { $0.0 }, wrap(matchable: title) { $0.1 }, wrap(matchable: closeAction) { $0.2 }, wrap(matchable: view) { $0.3 }] return .init(stub: cuckoo_manager.createStub(for: MockSettingsWireframeProtocol.self, method: "present(message: String?, title: String?, closeAction: String?, from: ControllerBackedProtocol?)", parameterMatchers: matchers)) @@ -20205,6 +20225,12 @@ import UIKit.UIImage return cuckoo_manager.verify("show(url: URL, from: ControllerBackedProtocol?)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } + @discardableResult + func showWalletConnect(from view: M1) -> Cuckoo.__DoNotUse<(ControllerBackedProtocol?), Void> where M1.OptionalMatchedType == ControllerBackedProtocol { + let matchers: [Cuckoo.ParameterMatcher<(ControllerBackedProtocol?)>] = [wrap(matchable: view) { $0 }] + return cuckoo_manager.verify("showWalletConnect(from: ControllerBackedProtocol?)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + @discardableResult func present(message: M1, title: M2, closeAction: M3, from view: M4) -> Cuckoo.__DoNotUse<(String?, String?, String?, ControllerBackedProtocol?), Void> where M1.OptionalMatchedType == String, M2.OptionalMatchedType == String, M3.OptionalMatchedType == String, M4.OptionalMatchedType == ControllerBackedProtocol { let matchers: [Cuckoo.ParameterMatcher<(String?, String?, String?, ControllerBackedProtocol?)>] = [wrap(matchable: message) { $0.0 }, wrap(matchable: title) { $0.1 }, wrap(matchable: closeAction) { $0.2 }, wrap(matchable: view) { $0.3 }] @@ -20276,6 +20302,12 @@ import UIKit.UIImage + func showWalletConnect(from view: ControllerBackedProtocol?) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + func present(message: String?, title: String?, closeAction: String?, from view: ControllerBackedProtocol?) { return DefaultValueRegistry.defaultValue(for: (Void).self) } From 42d7bb7e3d5a113f39ee737abb77f4cb8d589eb7 Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 25 Apr 2023 10:52:38 +0500 Subject: [PATCH 05/54] refactoring --- novawallet.xcodeproj/project.pbxproj | 46 +++++++++---------- .../Model/DAppAuthRequest.swift | 4 ++ .../DAppMetamaskWaitingAuthState.swift | 5 +- .../DAppBrowserWaitingAuthState.swift | 5 +- .../Transports/DAppTransports.swift | 1 + .../WalletConnectInteractor.swift | 0 .../WalletConnectPresenter.swift | 0 .../WalletConnectProtocols.swift | 0 .../WalletConnectViewController.swift | 0 .../WalletConnectViewFactory.swift | 0 .../WalletConnectViewLayout.swift | 0 .../WalletConnectWireframe.swift | 0 .../DAppAuthConfirmTests.swift | 5 +- 13 files changed, 40 insertions(+), 26 deletions(-) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectInteractor.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectPresenter.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectProtocols.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectViewController.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectViewFactory.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectViewLayout.swift (100%) rename novawallet/Modules/{ => DApp}/WalletConnect/WalletConnectWireframe.swift (100%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index f0261a1059..93a46dcb12 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -2608,13 +2608,13 @@ 8814774029B07B66001E98A1 /* DelegateGroupActionTitleControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814773F29B07B66001E98A1 /* DelegateGroupActionTitleControl.swift */; }; 8814774229B07E4C001E98A1 /* ResizableImageActionIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814774129B07E4C001E98A1 /* ResizableImageActionIndicator.swift */; }; 8814E858297E35D600A722FF /* GoveranaceDelegatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814E857297E35D600A722FF /* GoveranaceDelegatePicker.swift */; }; - 881CA22529E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */; }; - 881CA22729E3F20B00159C5B /* ChainModel+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */; }; - 881CA22929E406F700159C5B /* Paths+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */; }; 881BA30529DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */; }; 881BA30729DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30629DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift */; }; 881BA30929DC1B3000EB8C48 /* kilt-addresses.json in Resources */ = {isa = PBXBuildFile; fileRef = 881BA30829DC1B3000EB8C48 /* kilt-addresses.json */; }; 881BA30B29DC3DD400EB8C48 /* Data+baseEncoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30A29DC3DD400EB8C48 /* Data+baseEncoded.swift */; }; + 881CA22529E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */; }; + 881CA22729E3F20B00159C5B /* ChainModel+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */; }; + 881CA22929E406F700159C5B /* Paths+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */; }; 8824D4222902D92F0022D778 /* ReferendumFullDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D4212902D92F0022D778 /* ReferendumFullDetailsInteractor.swift */; }; 8824D424290324260022D778 /* PrettyPrintedJSONOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D423290324260022D778 /* PrettyPrintedJSONOperationFactory.swift */; }; 8824D42629032B410022D778 /* BlurredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D42529032B410022D778 /* BlurredView.swift */; }; @@ -2700,14 +2700,14 @@ 8860F3E2289D4FFD00C0BF86 /* SectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E1289D4FFD00C0BF86 /* SectionProtocol.swift */; }; 8860F3E4289D50BA00C0BF86 /* Array+SectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E3289D50BA00C0BF86 /* Array+SectionProtocol.swift */; }; 8860F3E8289D7CF400C0BF86 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E7289D7CF400C0BF86 /* Atomic.swift */; }; + 8863C7AC29D499D30068AD54 /* Web3NameService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AB29D499D30068AD54 /* Web3NameService.swift */; }; + 8863C7B029D49CB70068AD54 /* Web3NameViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */; }; 886A277E29E93995003B269F /* EquilibriumAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A277D29E93995003B269F /* EquilibriumAccountInfo.swift */; }; 886A278029E939D9003B269F /* EquilibriumAccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A277F29E939D9003B269F /* EquilibriumAccountData.swift */; }; 886A278229E939E1003B269F /* EquilibriumRemoteBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278129E939E1003B269F /* EquilibriumRemoteBalance.swift */; }; 886A278429E93A5D003B269F /* SignedBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278329E93A5D003B269F /* SignedBalance.swift */; }; 886A278629E93A84003B269F /* EquilibriumReservedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278529E93A84003B269F /* EquilibriumReservedData.swift */; }; 886A278829E93D2A003B269F /* CommonOperationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278729E93D2A003B269F /* CommonOperationWrapper.swift */; }; - 8863C7AC29D499D30068AD54 /* Web3NameService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AB29D499D30068AD54 /* Web3NameService.swift */; }; - 8863C7B029D49CB70068AD54 /* Web3NameViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */; }; 886CA9622977E9B300FC255A /* GovernanceDelegateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886CA9602977E9B200FC255A /* GovernanceDelegateTableViewCell.swift */; }; 886CA9632977E9B300FC255A /* GovernanceDelegateBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886CA9612977E9B200FC255A /* GovernanceDelegateBanner.swift */; }; 886E8CF81EF2566D98D9693E /* ExportSeedViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */; }; @@ -6038,13 +6038,13 @@ 8814773F29B07B66001E98A1 /* DelegateGroupActionTitleControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateGroupActionTitleControl.swift; sourceTree = ""; }; 8814774129B07E4C001E98A1 /* ResizableImageActionIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizableImageActionIndicator.swift; sourceTree = ""; }; 8814E857297E35D600A722FF /* GoveranaceDelegatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoveranaceDelegatePicker.swift; sourceTree = ""; }; - 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAssetBalanceUpdatingService.swift; sourceTree = ""; }; - 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChainModel+Equilibrium.swift"; sourceTree = ""; }; - 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Paths+Equilibrium.swift"; sourceTree = ""; }; 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifier.swift; sourceTree = ""; }; 881BA30629DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifierTests.swift; sourceTree = ""; }; 881BA30829DC1B3000EB8C48 /* kilt-addresses.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "kilt-addresses.json"; sourceTree = ""; }; 881BA30A29DC3DD400EB8C48 /* Data+baseEncoded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+baseEncoded.swift"; sourceTree = ""; }; + 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAssetBalanceUpdatingService.swift; sourceTree = ""; }; + 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChainModel+Equilibrium.swift"; sourceTree = ""; }; + 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Paths+Equilibrium.swift"; sourceTree = ""; }; 881ED96729758088000C0457 /* Array+safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+safe.swift"; sourceTree = ""; }; 8821119C96944A0E3526E93A /* StakingRedeemViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRedeemViewFactory.swift; sourceTree = ""; }; 8824D4212902D92F0022D778 /* ReferendumFullDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumFullDetailsInteractor.swift; sourceTree = ""; }; @@ -6131,14 +6131,14 @@ 8860F3E1289D4FFD00C0BF86 /* SectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionProtocol.swift; sourceTree = ""; }; 8860F3E3289D50BA00C0BF86 /* Array+SectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SectionProtocol.swift"; sourceTree = ""; }; 8860F3E7289D7CF400C0BF86 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; + 8863C7AB29D499D30068AD54 /* Web3NameService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameService.swift; sourceTree = ""; }; + 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameViewModelFactory.swift; sourceTree = ""; }; 886A277D29E93995003B269F /* EquilibriumAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAccountInfo.swift; sourceTree = ""; }; 886A277F29E939D9003B269F /* EquilibriumAccountData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquilibriumAccountData.swift; sourceTree = ""; }; 886A278129E939E1003B269F /* EquilibriumRemoteBalance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquilibriumRemoteBalance.swift; sourceTree = ""; }; 886A278329E93A5D003B269F /* SignedBalance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedBalance.swift; sourceTree = ""; }; 886A278529E93A84003B269F /* EquilibriumReservedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumReservedData.swift; sourceTree = ""; }; 886A278729E93D2A003B269F /* CommonOperationWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonOperationWrapper.swift; sourceTree = ""; }; - 8863C7AB29D499D30068AD54 /* Web3NameService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameService.swift; sourceTree = ""; }; - 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameViewModelFactory.swift; sourceTree = ""; }; 886CA9602977E9B200FC255A /* GovernanceDelegateTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateTableViewCell.swift; sourceTree = ""; }; 886CA9612977E9B200FC255A /* GovernanceDelegateBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateBanner.swift; sourceTree = ""; }; 887248072924F54900B0D2CC /* URL+Matchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Matchable.swift"; sourceTree = ""; }; @@ -9888,6 +9888,7 @@ 1673C6B5E37788C92D8E37D4 /* DAppAddFavorite */, 17C8516C4BEF61B6DE078C60 /* DAppAuthSettings */, 58F5C296A0CFDD0BDACDB4D7 /* DAppSettings */, + 1398F9A5DE987487202A580E /* WalletConnect */, ); path = DApp; sourceTree = ""; @@ -10738,7 +10739,6 @@ 018DE0E8A60963E2BDD94D13 /* YourWallets */, 83C426015DF863EBA46F1E3E /* Locks */, 9D97DD4BC9672502D2E2A625 /* TokensManage */, - 1398F9A5DE987487202A580E /* WalletConnect */, ); path = Modules; sourceTree = ""; @@ -14509,6 +14509,18 @@ path = View; sourceTree = ""; }; + 8863C7AA29D499BC0068AD54 /* Web3Name */ = { + isa = PBXGroup; + children = ( + 8863C7AB29D499D30068AD54 /* Web3NameService.swift */, + 88F33F1429CC1F92006125D5 /* Slip44CoinList.swift */, + 8886020A29D101B000C6344C /* Web3NameServiceError.swift */, + 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */, + 84C1E7C829EE990800D37668 /* Web3NameProvider.swift */, + ); + path = Web3Name; + sourceTree = ""; + }; 886A277C29E9398B003B269F /* Model */ = { isa = PBXGroup; children = ( @@ -14523,18 +14535,6 @@ path = Model; sourceTree = ""; }; - 8863C7AA29D499BC0068AD54 /* Web3Name */ = { - isa = PBXGroup; - children = ( - 8863C7AB29D499D30068AD54 /* Web3NameService.swift */, - 88F33F1429CC1F92006125D5 /* Slip44CoinList.swift */, - 8886020A29D101B000C6344C /* Web3NameServiceError.swift */, - 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */, - 84C1E7C829EE990800D37668 /* Web3NameProvider.swift */, - ); - path = Web3Name; - sourceTree = ""; - }; 886CA96429785D9200FC255A /* Model */ = { isa = PBXGroup; children = ( diff --git a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift index 5dd30b60a4..0af07af2f0 100644 --- a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift +++ b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift @@ -7,6 +7,10 @@ struct DAppAuthRequest { let origin: String? let dApp: String let dAppIcon: URL? + + let requiredChains: Set + let optionalChains: Set? + let unknownChains: Set? } struct DAppAuthResponse { diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift index 61724bd784..c93adc78fe 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift @@ -51,7 +51,10 @@ final class DAppMetamaskWaitingAuthState: DAppMetamaskBaseState { wallet: dataSource.wallet, origin: host, dApp: host, - dAppIcon: nil + dAppIcon: nil, + requiredChains: Set(), + optionalChains: nil, + unknownChains: nil ) let nextState = DAppMetamaskAuthorizingState( diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift index 5036317269..3d5b8938d2 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift @@ -47,7 +47,10 @@ final class DAppBrowserWaitingAuthState: DAppBrowserBaseState { wallet: dataSource.wallet, origin: message.request?.origin?.stringValue, dApp: message.url ?? "", - dAppIcon: dataSource.dApp?.icon + dAppIcon: dataSource.dApp?.icon, + requiredChains: [], + optionalChains: nil, + unknownChains: nil ) let nextState = DAppBrowserAuthorizingState( diff --git a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift index ff21077d4d..653dffcd06 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift @@ -3,4 +3,5 @@ import Foundation enum DAppTransports { static let polkadotExtension = "_nova_" static let metamask = "_metamask_" + static let walletConnect = "_wc_" } diff --git a/novawallet/Modules/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectInteractor.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectPresenter.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectPresenter.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectProtocols.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewController.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectViewController.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectViewFactory.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectViewLayout.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift diff --git a/novawallet/Modules/WalletConnect/WalletConnectWireframe.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift similarity index 100% rename from novawallet/Modules/WalletConnect/WalletConnectWireframe.swift rename to novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift diff --git a/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift b/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift index e6aaea3bac..faa7bd1c72 100644 --- a/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift +++ b/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift @@ -110,7 +110,10 @@ class DAppAuthConfirmTests: XCTestCase { wallet: walletSettings.value, origin: "DApp", dApp: "Test", - dAppIcon: nil + dAppIcon: nil, + requiredChains: [], + optionalChains: nil, + unknownChains: nil ) let presenter = DAppAuthConfirmPresenter( From f2b70e1070ac5ceff60a320cde1dc272d08a1013 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 26 Apr 2023 11:53:34 +0500 Subject: [PATCH 06/54] add dapp interaction mediator --- novawallet.xcodeproj/project.pbxproj | 42 ++++- .../Helpers/AccountRepositoryFactory.swift | 7 +- .../DAppAuthConfirmPresenter.swift | 2 +- .../Model/DAppAuthRequest.swift | 2 + .../DAppInteractionError.swift | 5 + .../DAppInteractionFactory.swift | 30 +++ .../DAppInteractionMediator.swift | 171 ++++++++++++++++++ .../DAppInteractionPresenter.swift | 99 ++++++++++ .../DAppInteractionProtocols.swift | 40 ++++ .../DAppTransports.swift | 0 .../Model/DAppStateDataSource.swift | 18 ++ .../WalletConnectInteractor.swift | 2 +- 12 files changed, 412 insertions(+), 6 deletions(-) create mode 100644 novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift create mode 100644 novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift create mode 100644 novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift create mode 100644 novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift create mode 100644 novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift rename novawallet/Modules/DApp/{DAppBrowser/Transports => DAppInteraction}/DAppTransports.swift (100%) create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 93a46dcb12..6dd25495f1 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1819,6 +1819,9 @@ 849F144D29444CB800D9F9BA /* AssetModel+TokenAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F144C29444CB800D9F9BA /* AssetModel+TokenAddRequest.swift */; }; 849F144F29445A4600D9F9BA /* EvmInputViewModel+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F144E29445A4600D9F9BA /* EvmInputViewModel+Handler.swift */; }; 849F1453294477DA00D9F9BA /* TextWithServiceInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F1452294477DA00D9F9BA /* TextWithServiceInputView.swift */; }; + 849F33B729F7B270001AEFA4 /* DAppStateDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */; }; + 849F33BA29F7BA86001AEFA4 /* DAppInteractionMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F33B929F7BA86001AEFA4 /* DAppInteractionMediator.swift */; }; + 849F33BC29F7C659001AEFA4 /* DAppInteractionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849F33BB29F7C659001AEFA4 /* DAppInteractionProtocols.swift */; }; 849FA21628A26CB500F83EAA /* CountdownTimerMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849FA21528A26CB500F83EAA /* CountdownTimerMediator.swift */; }; 849FFFCD2980116A00A17471 /* GovernanceDelegateInfoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849FFFCC2980116A00A17471 /* GovernanceDelegateInfoError.swift */; }; 849FFFD0298020C900A17471 /* GovernanceDelegationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849FFFCF298020C900A17471 /* GovernanceDelegationConstants.swift */; }; @@ -1985,6 +1988,9 @@ 84B7C747289BFA79001A3566 /* AccountManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7C706289BFA79001A3566 /* AccountManagementTests.swift */; }; 84B7C748289BFA79001A3566 /* WalletListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7C709289BFA79001A3566 /* WalletListTests.swift */; }; 84B7C749289BFA79001A3566 /* ControllerAccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7C70B289BFA79001A3566 /* ControllerAccountTests.swift */; }; + 84B8AA7129F8E59300347A37 /* DAppInteractionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */; }; + 84B8AA7329F8EFC700347A37 /* DAppInteractionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */; }; + 84B8AA7529F8FD2400347A37 /* DAppInteractionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */; }; 84B91AED287078B0001A0420 /* StorageProviderSourceFallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */; }; 84BA1F7E27CE23C1000AAB82 /* NftChainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */; }; 84BAB6102642C286007782D0 /* SelectedRebondVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */; }; @@ -5240,6 +5246,9 @@ 849F144C29444CB800D9F9BA /* AssetModel+TokenAddRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AssetModel+TokenAddRequest.swift"; sourceTree = ""; }; 849F144E29445A4600D9F9BA /* EvmInputViewModel+Handler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EvmInputViewModel+Handler.swift"; sourceTree = ""; }; 849F1452294477DA00D9F9BA /* TextWithServiceInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithServiceInputView.swift; sourceTree = ""; }; + 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppStateDataSource.swift; sourceTree = ""; }; + 849F33B929F7BA86001AEFA4 /* DAppInteractionMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionMediator.swift; sourceTree = ""; }; + 849F33BB29F7C659001AEFA4 /* DAppInteractionProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionProtocols.swift; sourceTree = ""; }; 849FA21528A26CB500F83EAA /* CountdownTimerMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownTimerMediator.swift; sourceTree = ""; }; 849FFFCC2980116A00A17471 /* GovernanceDelegateInfoError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateInfoError.swift; sourceTree = ""; }; 849FFFCF298020C900A17471 /* GovernanceDelegationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceDelegationConstants.swift; sourceTree = ""; }; @@ -5407,6 +5416,9 @@ 84B7C706289BFA79001A3566 /* AccountManagementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManagementTests.swift; sourceTree = ""; }; 84B7C709289BFA79001A3566 /* WalletListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletListTests.swift; sourceTree = ""; }; 84B7C70B289BFA79001A3566 /* ControllerAccountTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerAccountTests.swift; sourceTree = ""; }; + 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionPresenter.swift; sourceTree = ""; }; + 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionError.swift; sourceTree = ""; }; + 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionFactory.swift; sourceTree = ""; }; 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageProviderSourceFallback.swift; sourceTree = ""; }; 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftChainModel.swift; sourceTree = ""; }; 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedRebondVariant.swift; sourceTree = ""; }; @@ -7057,6 +7069,7 @@ 1398F9A5DE987487202A580E /* WalletConnect */ = { isa = PBXGroup; children = ( + 849F33B529F7B225001AEFA4 /* Model */, 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */, F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */, 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */, @@ -9875,6 +9888,7 @@ 846B49A92769EEC80066B875 /* DApp */ = { isa = PBXGroup; children = ( + 849F33B829F7BA0F001AEFA4 /* DAppInteraction */, 84D17EDF2805A5ED00F7BAFF /* Protocols */, 84D2F1A3277437C00040C680 /* View */, 84EC2D1B276C67EB009B0BE1 /* Model */, @@ -11658,7 +11672,6 @@ 849976B227B2430300B14A6C /* DAppMetamaskTransport.swift */, 849976B727B24BCB00B14A6C /* DAppPolkadotExtensionTransport.swift */, 849976B927B24C2200B14A6C /* DAppBrowserTransportProtocol.swift */, - 849976BD27B269A400B14A6C /* DAppTransports.swift */, ); path = Transports; sourceTree = ""; @@ -11839,6 +11852,27 @@ path = Model; sourceTree = ""; }; + 849F33B529F7B225001AEFA4 /* Model */ = { + isa = PBXGroup; + children = ( + 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */, + ); + path = Model; + sourceTree = ""; + }; + 849F33B829F7BA0F001AEFA4 /* DAppInteraction */ = { + isa = PBXGroup; + children = ( + 849976BD27B269A400B14A6C /* DAppTransports.swift */, + 849F33B929F7BA86001AEFA4 /* DAppInteractionMediator.swift */, + 849F33BB29F7C659001AEFA4 /* DAppInteractionProtocols.swift */, + 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */, + 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */, + 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */, + ); + path = DAppInteraction; + sourceTree = ""; + }; 849FFFCE298020B400A17471 /* Model */ = { isa = PBXGroup; children = ( @@ -16706,6 +16740,7 @@ F462B364260C88050005AB01 /* UITableView+Reuse.swift in Sources */, 84AE7AA527D35A9800495267 /* StackTableView.swift in Sources */, 88AA0FB828B60E6A00931800 /* YourWalletsControlView.swift in Sources */, + 849F33B729F7B270001AEFA4 /* DAppStateDataSource.swift in Sources */, 84EE2FA9289120A100A98816 /* WalletManagePresenter.swift in Sources */, 842A737727DC7AEB006EE1EA /* NetworkViewModelFactory.swift in Sources */, 848919D726FB238E004DBAD5 /* JsonDataProviderFactory.swift in Sources */, @@ -17176,6 +17211,7 @@ 84FD3DB92540F70400A234E3 /* TransactionHistoryItem+Extrinsic.swift in Sources */, 849A4EF8279ABBDD00AB6709 /* AssetBalance.swift in Sources */, 88A0E10128A284C800A9C940 /* Observable.swift in Sources */, + 84B8AA7529F8FD2400347A37 /* DAppInteractionFactory.swift in Sources */, 844EFB65265FD61D0090ACB1 /* CrowdloanContributeConfirmViewModel.swift in Sources */, AEE5FAFF26415E0C002B8FDC /* StakingRebondSetupPresenter.swift in Sources */, 84A3B8A02836D74B00DE2669 /* StorageKeyDecodingProtocol.swift in Sources */, @@ -17591,6 +17627,7 @@ AEA0C8C12681180900F9666F /* InitBondingCustomValidatorListWireframe.swift in Sources */, 849976C127B2823F00B14A6C /* DAppMetamaskBaseState.swift in Sources */, 842876A624AE049B00D91AD8 /* SelectableViewModelProtocol.swift in Sources */, + 84B8AA7129F8E59300347A37 /* DAppInteractionPresenter.swift in Sources */, 8831F10028C65B95009F7682 /* AssetLock.swift in Sources */, 842B1806286506EE0014CC57 /* CrossChainTransferSetupProtocols.swift in Sources */, 84002AA52992516D00A80672 /* GovernanceDelegateState.swift in Sources */, @@ -17805,6 +17842,7 @@ 84468A072866530100BCBE00 /* AssetStorageInfoOperationFactory.swift in Sources */, 84744953289268770042FD80 /* WalletSwitchViewModel.swift in Sources */, 84FACCD925F8C22A00F32ED4 /* BigInt+Hex.swift in Sources */, + 849F33BC29F7C659001AEFA4 /* DAppInteractionProtocols.swift in Sources */, F409672626B29B04008CD244 /* UIScrollView+ScrollToPage.swift in Sources */, 84E0EE0E292D69A9008B2953 /* CallMetadata+TypeCheck.swift in Sources */, 844384AE28538F4700611CE2 /* AlephZeroRewardEngine.swift in Sources */, @@ -18362,6 +18400,7 @@ 84644A0C256713D5004EAA4B /* TriangularedBlurView+Inspectable.swift in Sources */, 849A4EF6279A7AEF00AB6709 /* StateminAssetExtras.swift in Sources */, 849E17E627914394002D1744 /* NavigationBarSettings.swift in Sources */, + 84B8AA7329F8EFC700347A37 /* DAppInteractionError.swift in Sources */, 849E17F02791909C002D1744 /* DAppSettings.swift in Sources */, 84DBEA7729DAF5EC00A504A7 /* ConnectionNodeSwitchCode.swift in Sources */, 884048D428C723F00085FFA6 /* OrmlTokenSubscriptionHandlingFactory.swift in Sources */, @@ -19762,6 +19801,7 @@ 6B18B9B1EB93B1341B95E403 /* CommonDelegationTracksViewController.swift in Sources */, A9A4846AB91BCB88E0416E38 /* CommonDelegationTracksViewLayout.swift in Sources */, 0754911527A21957BD25A1DA /* CommonDelegationTracksViewFactory.swift in Sources */, + 849F33BA29F7BA86001AEFA4 /* DAppInteractionMediator.swift in Sources */, 845B823629C8FF2900D187CB /* EtherscanBaseOperationFactory.swift in Sources */, 473EDA64B1A18BA80189142D /* GovernanceRemoveVotesConfirmProtocols.swift in Sources */, 590717389ECBA34FA08F247B /* GovernanceRemoveVotesConfirmWireframe.swift in Sources */, diff --git a/novawallet/Common/Helpers/AccountRepositoryFactory.swift b/novawallet/Common/Helpers/AccountRepositoryFactory.swift index c7812e5faa..dc526e2c23 100644 --- a/novawallet/Common/Helpers/AccountRepositoryFactory.swift +++ b/novawallet/Common/Helpers/AccountRepositoryFactory.swift @@ -15,7 +15,7 @@ protocol AccountRepositoryFactoryProtocol { func createFavoriteDAppsRepository() -> AnyDataProviderRepository - func createAuthorizedDAppsRepository(for metaId: String) -> AnyDataProviderRepository + func createAuthorizedDAppsRepository(for metaId: String?) -> AnyDataProviderRepository func createDAppsGlobalSettingsRepository() -> AnyDataProviderRepository } @@ -85,9 +85,10 @@ final class AccountRepositoryFactory: AccountRepositoryFactoryProtocol { return AnyDataProviderRepository(repository) } - func createAuthorizedDAppsRepository(for metaId: String) -> AnyDataProviderRepository { + func createAuthorizedDAppsRepository(for metaId: String?) -> AnyDataProviderRepository { let mapper = DAppSettingsMapper() - let filter = NSPredicate.filterAuthorizedDApps(by: metaId) + + let filter = metaId.map { NSPredicate.filterAuthorizedDApps(by: $0) } let repository = storageFacade.createRepository( filter: filter, sortDescriptors: [], diff --git a/novawallet/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmPresenter.swift b/novawallet/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmPresenter.swift index d86f37eeb5..892782a993 100644 --- a/novawallet/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmPresenter.swift +++ b/novawallet/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmPresenter.swift @@ -22,7 +22,7 @@ final class DAppAuthConfirmPresenter { } private func complete(with result: Bool) { - let response = DAppAuthResponse(approved: result) + let response = DAppAuthResponse(approved: result, wallet: request.wallet) delegate?.didReceiveAuthResponse(response, for: request) wireframe.close(from: view) } diff --git a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift index 0af07af2f0..acac5afa43 100644 --- a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift +++ b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift @@ -15,4 +15,6 @@ struct DAppAuthRequest { struct DAppAuthResponse { let approved: Bool + + let wallet: MetaAccountModel } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift new file mode 100644 index 0000000000..8acbcc5b1f --- /dev/null +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift @@ -0,0 +1,5 @@ +import Foundation + +enum DAppInteractionError: Error { + case phishingVerifierFailed(Error) +} diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift new file mode 100644 index 0000000000..63fbde1050 --- /dev/null +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift @@ -0,0 +1,30 @@ +import Foundation + +final class DAppInteractionFactory { + func createMediator() -> DAppInteractionMediating { + let logger = Logger.shared + + let presenter = DAppInteractionPresenter(logger: logger) + + let storageFacade = UserDataStorageFacade.shared + let accountRepositoryFactory = AccountRepositoryFactory(storageFacade: storageFacade) + let settingsRepository = accountRepositoryFactory.createAuthorizedDAppsRepository(for: nil) + + let phishingVerifier = PhishingSiteVerifier.createSequentialVerifier() + + let mediator = DAppInteractionMediator( + presenter: presenter, + children: [], + chainRegistry: ChainRegistryFacade.sharedRegistry, + settingsRepository: settingsRepository, + securedLayer: SecurityLayerService.shared, + sequentialPhishingVerifier: phishingVerifier, + operationQueue: OperationManagerFacade.sharedDefaultQueue, + logger: logger + ) + + presenter.interactor = mediator + + return mediator + } +} diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift new file mode 100644 index 0000000000..b9ae50fbdf --- /dev/null +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift @@ -0,0 +1,171 @@ +import Foundation +import RobinHood + +final class DAppInteractionMediator { + struct QueueMessage { + let host: String + let transportName: String + let underliningMessage: Any + } + + let presenter: DAppInteractionOutputProtocol + + let chainsStore: ChainsStoreProtocol + let settingsRepository: AnyDataProviderRepository + let securedLayer: SecurityLayerServiceProtocol + let sequentialPhishingVerifier: PhishingSiteVerifing + let operationQueue: OperationQueue + let logger: LoggerProtocol? + + private(set) var messageQueue: [QueueMessage] = [] + private(set) var transports: [DAppTransportProtocol] = [] + + let children: [DAppInteractionChildProtocol] + + init( + presenter: DAppInteractionOutputProtocol, + children: [DAppInteractionChildProtocol], + chainRegistry: ChainRegistryProtocol, + settingsRepository: AnyDataProviderRepository, + securedLayer: SecurityLayerServiceProtocol, + sequentialPhishingVerifier: PhishingSiteVerifing, + operationQueue: OperationQueue, + logger: LoggerProtocol? + ) { + self.presenter = presenter + self.children = children + chainsStore = ChainsStore(chainRegistry: chainRegistry) + self.settingsRepository = settingsRepository + self.securedLayer = securedLayer + self.sequentialPhishingVerifier = sequentialPhishingVerifier + self.operationQueue = operationQueue + self.logger = logger + } + + private func processMessageIfNeeded() { + guard transports.allSatisfy({ $0.isIdle() }), let queueMessage = messageQueue.first else { + return + } + + messageQueue.removeFirst() + + let transport = transports.first { $0.name == queueMessage.transportName } + + transport?.process(message: queueMessage.underliningMessage, host: queueMessage.host) + } + + private func bringPhishingDetectedStateAndNotify(for host: String) { + let allNotPhishing = transports + .map { $0.bringPhishingDetectedStateIfNeeded() } + .allSatisfy { !$0 } + + if !allNotPhishing { + presenter.didDetectPhishing(host: host) + } + } + + private func verifyPhishing(for host: String, completion: ((Bool) -> Void)?) { + sequentialPhishingVerifier.verify(host: host) { [weak self] result in + switch result { + case let .success(isNotPhishing): + if !isNotPhishing { + self?.bringPhishingDetectedStateAndNotify(for: host) + } + + completion?(isNotPhishing) + case let .failure(error): + self?.presenter.didReceive(error: .phishingVerifierFailed(error)) + } + } + } +} + +extension DAppInteractionMediator: DAppInteractionMediating { + func register(transport: DAppTransportProtocol) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + guard let self = self else { + return + } + + guard !self.transports.contains(where: { $0 !== transport }) else { + return + } + + self.transports.append(transport) + } + } + + func unregister(transport: DAppTransportProtocol) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + guard let self = self else { + return + } + + self.transports = self.transports.filter { $0 !== transport } + } + } + + func process(message: Any, host: String, transport name: String) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.logger?.debug("Did receive \(name) message from \(host): \(message)") + + self?.verifyPhishing(for: host) { [weak self] isNotPhishing in + if isNotPhishing { + let queueMessage = QueueMessage( + host: host, + transportName: name, + underliningMessage: message + ) + self?.messageQueue.append(queueMessage) + + self?.processMessageIfNeeded() + } + } + } + } + + func process(authRequest: DAppAuthRequest) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.presenter.didReceiveAuth(request: authRequest) + } + } + + func process(signingRequest: DAppOperationRequest, type: DAppSigningType) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.presenter.didReceiveConfirmation(request: signingRequest, type: type) + } + } +} + +extension DAppInteractionMediator: DAppInteractionInputProtocol { + func processConfirmation(response: DAppOperationResponse, forTransport name: String) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.transports.first(where: { $0.name == name })?.processConfirmation(response: response) + } + } + + func processAuth(response: DAppAuthResponse, forTransport name: String) { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.transports.first(where: { $0.name == name })?.processAuth(response: response) + } + } +} + +extension DAppInteractionMediator: ChainsStoreDelegate { + func didUpdateChainsStore(_: ChainsStoreProtocol) { + transports.forEach { $0.processChainsChanges() } + } +} + +extension DAppInteractionMediator: ApplicationServiceProtocol { + func setup() { + chainsStore.delegate = self + chainsStore.setup() + + children.forEach { $0.setup() } + } + + func throttle() { + children.forEach { $0.throttle() } + } +} diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift new file mode 100644 index 0000000000..72e1d4bfc4 --- /dev/null +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift @@ -0,0 +1,99 @@ +import UIKit +import SoraUI + +final class DAppInteractionPresenter { + var window: UIWindow? { UIApplication.shared.keyWindow } + + weak var interactor: DAppInteractionInputProtocol? + + let logger: LoggerProtocol + + init(logger: LoggerProtocol) { + self.logger = logger + } +} + +extension DAppInteractionPresenter: DAppInteractionOutputProtocol { + func didReceiveConfirmation(request: DAppOperationRequest, type: DAppSigningType) { + guard let confirmationView = DAppOperationConfirmViewFactory.createView( + for: request, + type: type, + delegate: self + ) else { + return + } + + let factory = ModalSheetPresentationFactory(configuration: ModalSheetPresentationConfiguration.nova + ) + + confirmationView.controller.modalTransitioningFactory = factory + confirmationView.controller.modalPresentationStyle = .custom + + window?.rootViewController?.topModalViewController.present( + confirmationView.controller, + animated: true, + completion: nil + ) + } + + func didReceiveAuth(request: DAppAuthRequest) { + guard let authVew = DAppAuthConfirmViewFactory.createView(for: request, delegate: self) else { + return + } + + let factory = ModalSheetPresentationFactory( + configuration: ModalSheetPresentationConfiguration.nova + ) + authVew.controller.modalTransitioningFactory = factory + authVew.controller.modalPresentationStyle = .custom + + window?.rootViewController?.topModalViewController.present( + authVew.controller, + animated: true, + completion: nil + ) + } + + func didDetectPhishing(host _: String) { + guard let phishingView = DAppPhishingViewFactory.createView(with: self) else { + return + } + + let factory = ModalSheetPresentationFactory( + configuration: ModalSheetPresentationConfiguration.nova + ) + phishingView.controller.modalTransitioningFactory = factory + phishingView.controller.modalPresentationStyle = .custom + + window?.rootViewController?.topModalViewController.present( + phishingView.controller, + animated: true, + completion: nil + ) + } + + func didReceive(error: DAppInteractionError) { + logger.error("Did receive error: \(error)") + } +} + +extension DAppInteractionPresenter: DAppAuthDelegate { + func didReceiveAuthResponse(_ response: DAppAuthResponse, for request: DAppAuthRequest) { + interactor?.processAuth(response: response, forTransport: request.transportName) + } +} + +extension DAppInteractionPresenter: DAppOperationConfirmDelegate { + func didReceiveConfirmationResponse( + _ response: DAppOperationResponse, + for request: DAppOperationRequest + ) { + interactor?.processConfirmation(response: response, forTransport: request.transportName) + } +} + +extension DAppInteractionPresenter: DAppPhishingViewDelegate { + func dappPhishingViewDidHide() { + // TODO: we might need to notify child ui to hide phishing interface + } +} diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift new file mode 100644 index 0000000000..0f516d13dc --- /dev/null +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift @@ -0,0 +1,40 @@ +import Foundation +import RobinHood + +protocol DAppTransportProtocol: AnyObject { + var name: String { get } + + func isIdle() -> Bool + func bringPhishingDetectedStateIfNeeded() -> Bool + func process(message: Any, host: String) + func processConfirmation(response: DAppOperationResponse) + func processAuth(response: DAppAuthResponse) + func processChainsChanges() +} + +protocol DAppInteractionMediating { + var chainsStore: ChainsStoreProtocol { get } + var settingsRepository: AnyDataProviderRepository { get } + var operationQueue: OperationQueue { get } + + func register(transport: DAppTransportProtocol) + func unregister(transport: DAppTransportProtocol) + + func process(message: Any, host: String, transport name: String) + func process(authRequest: DAppAuthRequest) + func process(signingRequest: DAppOperationRequest, type: DAppSigningType) +} + +protocol DAppInteractionInputProtocol: AnyObject { + func processConfirmation(response: DAppOperationResponse, forTransport name: String) + func processAuth(response: DAppAuthResponse, forTransport name: String) +} + +protocol DAppInteractionOutputProtocol: AnyObject { + func didReceiveConfirmation(request: DAppOperationRequest, type: DAppSigningType) + func didReceiveAuth(request: DAppAuthRequest) + func didDetectPhishing(host: String) + func didReceive(error: DAppInteractionError) +} + +protocol DAppInteractionChildProtocol: ApplicationServiceProtocol {} diff --git a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift b/novawallet/Modules/DApp/DAppInteraction/DAppTransports.swift similarity index 100% rename from novawallet/Modules/DApp/DAppBrowser/Transports/DAppTransports.swift rename to novawallet/Modules/DApp/DAppInteraction/DAppTransports.swift diff --git a/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift b/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift new file mode 100644 index 0000000000..032f118ffb --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift @@ -0,0 +1,18 @@ +import Foundation +import RobinHood + +final class DAppStateDataSource { + let chainsStore: ChainsStoreProtocol + let dAppSettingsRepository: AnyDataProviderRepository + let operationQueue: OperationQueue + + init( + chainsStore: ChainsStoreProtocol, + dAppSettingsRepository: AnyDataProviderRepository, + operationQueue: OperationQueue + ) { + self.chainsStore = chainsStore + self.dAppSettingsRepository = dAppSettingsRepository + self.operationQueue = operationQueue + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift index ae12147f49..b3b51f6933 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift @@ -42,6 +42,6 @@ extension WalletConnectInteractor: WalletConnectServiceDelegate { } func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { - logger.debug("Error: \(error)") + logger.error("Error: \(error)") } } From fa7093918a6f44be60c41d28f172d95b86fd67cd Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Wed, 26 Apr 2023 12:18:42 +0400 Subject: [PATCH 07/54] fix --- .../Common/Web3Names/Web3TransferRecipientRepository.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/novawallet/Common/Web3Names/Web3TransferRecipientRepository.swift b/novawallet/Common/Web3Names/Web3TransferRecipientRepository.swift index b451a082d6..7aa833e00b 100644 --- a/novawallet/Common/Web3Names/Web3TransferRecipientRepository.swift +++ b/novawallet/Common/Web3Names/Web3TransferRecipientRepository.swift @@ -32,7 +32,7 @@ final class KiltTransferAssetRecipientRepository: BaseFetchOperationFactory { if let hash = hash { let isValid = self.integrityVerifier.verify( serviceEndpointId: hash, - serviceEndpointContent: content.trimmingCharacters(in: .whitespacesAndNewlines) + serviceEndpointContent: content ) guard isValid else { From f6eadae6b11663f7a077610198fa838e529403e0 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 26 Apr 2023 14:08:37 +0500 Subject: [PATCH 08/54] add states --- novawallet.xcodeproj/project.pbxproj | 48 +++++++++++++++++++ .../WalletConnectStateAuthorizing.swift | 9 ++++ .../States/WalletConnectStateError.swift | 5 ++ .../States/WalletConnectStateInitiating.swift | 9 ++++ .../States/WalletConnectStateMessage.swift | 7 +++ .../States/WalletConnectStateProtocols.swift | 23 +++++++++ .../States/WalletConnectStateReady.swift | 9 ++++ .../States/WalletConnectStateSigning.swift | 9 ++++ .../Transport/WalletConnectTransport.swift | 45 +++++++++++++++++ 9 files changed, 164 insertions(+) create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 6dd25495f1..13eeeb32e1 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1991,6 +1991,14 @@ 84B8AA7129F8E59300347A37 /* DAppInteractionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */; }; 84B8AA7329F8EFC700347A37 /* DAppInteractionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */; }; 84B8AA7529F8FD2400347A37 /* DAppInteractionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */; }; + 84B8AA7929F905C800347A37 /* WalletConnectStateInitiating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7829F905C800347A37 /* WalletConnectStateInitiating.swift */; }; + 84B8AA7B29F905ED00347A37 /* WalletConnectStateReady.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7A29F905ED00347A37 /* WalletConnectStateReady.swift */; }; + 84B8AA7D29F9063400347A37 /* WalletConnectStateAuthorizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7C29F9063400347A37 /* WalletConnectStateAuthorizing.swift */; }; + 84B8AA7F29F9064300347A37 /* WalletConnectStateSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA7E29F9064300347A37 /* WalletConnectStateSigning.swift */; }; + 84B8AA8129F9097D00347A37 /* WalletConnectTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */; }; + 84B8AA8329F90E3E00347A37 /* WalletConnectStateProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */; }; + 84B8AA8529F910AD00347A37 /* WalletConnectStateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */; }; + 84B8AA8729F9115400347A37 /* WalletConnectStateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */; }; 84B91AED287078B0001A0420 /* StorageProviderSourceFallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */; }; 84BA1F7E27CE23C1000AAB82 /* NftChainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */; }; 84BAB6102642C286007782D0 /* SelectedRebondVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */; }; @@ -5419,6 +5427,14 @@ 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionPresenter.swift; sourceTree = ""; }; 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionError.swift; sourceTree = ""; }; 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppInteractionFactory.swift; sourceTree = ""; }; + 84B8AA7829F905C800347A37 /* WalletConnectStateInitiating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateInitiating.swift; sourceTree = ""; }; + 84B8AA7A29F905ED00347A37 /* WalletConnectStateReady.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateReady.swift; sourceTree = ""; }; + 84B8AA7C29F9063400347A37 /* WalletConnectStateAuthorizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateAuthorizing.swift; sourceTree = ""; }; + 84B8AA7E29F9064300347A37 /* WalletConnectStateSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateSigning.swift; sourceTree = ""; }; + 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectTransport.swift; sourceTree = ""; }; + 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateProtocols.swift; sourceTree = ""; }; + 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateError.swift; sourceTree = ""; }; + 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateMessage.swift; sourceTree = ""; }; 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageProviderSourceFallback.swift; sourceTree = ""; }; 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftChainModel.swift; sourceTree = ""; }; 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedRebondVariant.swift; sourceTree = ""; }; @@ -7069,6 +7085,8 @@ 1398F9A5DE987487202A580E /* WalletConnect */ = { isa = PBXGroup; children = ( + 84B8AA7729F9049900347A37 /* States */, + 84B8AA7629F9048F00347A37 /* Transport */, 849F33B529F7B225001AEFA4 /* Model */, 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */, F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */, @@ -12826,6 +12844,28 @@ path = ControllerAccount; sourceTree = ""; }; + 84B8AA7629F9048F00347A37 /* Transport */ = { + isa = PBXGroup; + children = ( + 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */, + ); + path = Transport; + sourceTree = ""; + }; + 84B8AA7729F9049900347A37 /* States */ = { + isa = PBXGroup; + children = ( + 84B8AA7829F905C800347A37 /* WalletConnectStateInitiating.swift */, + 84B8AA7A29F905ED00347A37 /* WalletConnectStateReady.swift */, + 84B8AA7C29F9063400347A37 /* WalletConnectStateAuthorizing.swift */, + 84B8AA7E29F9064300347A37 /* WalletConnectStateSigning.swift */, + 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */, + 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */, + 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */, + ); + path = States; + sourceTree = ""; + }; 84BAD20E293A2CF200C55C49 /* View */ = { isa = PBXGroup; children = ( @@ -16957,6 +16997,7 @@ 849D755D27577602007726C3 /* AccountExportPasswordViewLayout.swift in Sources */, 849628CB299684D70073F143 /* GovernanceYourDelegationGroup.swift in Sources */, 8860F3E0289D241E00C0BF86 /* CurrencyViewSectionModel.swift in Sources */, + 84B8AA7D29F9063400347A37 /* WalletConnectStateAuthorizing.swift in Sources */, 842806F32847A51400702F3A /* AccountDetailsSelectionView.swift in Sources */, 840B3D652899BBF100DA1DA9 /* ParitySignerScanViewFactory.swift in Sources */, 84C5ADDB2812A244006D7388 /* WalletAccountViewModelFactory.swift in Sources */, @@ -17416,6 +17457,7 @@ 840D92A7278EE8690007B979 /* DAppSignBytesConfirmInteractor.swift in Sources */, 84EC2D1A276C6600009B0BE1 /* PolkadotExtensionExtrinsic.swift in Sources */, 84A8FD8E265FDA76002ADB58 /* CrowdloanContributionConfirmData.swift in Sources */, + 84B8AA8729F9115400347A37 /* WalletConnectStateMessage.swift in Sources */, 84F98D9625E3E2B10040418E /* InMemoryDataProviderRepository.swift in Sources */, 8446F5FA28192FF500B7A86C /* ListLoadingView.swift in Sources */, 844384AC28538D3000611CE2 /* RewardCalculatorEngine.swift in Sources */, @@ -18048,6 +18090,7 @@ 8487584B27F1834E00495306 /* ImageGalleryPresentable.swift in Sources */, 8427495328FEB8C800B2B70B /* ReferendumNewVote.swift in Sources */, AE20602C2636EA5800357578 /* MercuryoKeys.swift in Sources */, + 84B8AA7B29F905ED00347A37 /* WalletConnectStateReady.swift in Sources */, 840B3D70289A575A00DA1DA9 /* ParitySignerScanProtocols.swift in Sources */, 849067C8299BCB0100B2983E /* GovernanceRevokeDelegationConfirmPresenter+Protocol.swift in Sources */, 8452585327ABCA07004F9082 /* HideZeroBalancesChanged.swift in Sources */, @@ -18358,6 +18401,7 @@ 849B563527A70DDE007D5528 /* ExtrinsicProcessor+Matching.swift in Sources */, 8804AD8D295B7735001C4E09 /* DAppGlobalSettingsMapper.swift in Sources */, 94B0F0C84AF74B3CD7223C3A /* AccountConfirmPresenter.swift in Sources */, + 84B8AA7929F905C800347A37 /* WalletConnectStateInitiating.swift in Sources */, 8468B87224F63D3A00B76BC6 /* AddAccount+AccountCreateWireframe.swift in Sources */, 9DFB37659A6B911A4D54623E /* AccountConfirmInteractor.swift in Sources */, 84BAFCD626AF64CB00871E86 /* SelectValidatorsViewLayout.swift in Sources */, @@ -18846,6 +18890,7 @@ 846AC7EF2638D9200075F7DA /* YourValidatorTableCell.swift in Sources */, C937154FA9021AECD72A871B /* StakingRewardDetailsViewController.swift in Sources */, 84770F27291F7CD400852A33 /* ReferendumDetailsInitData.swift in Sources */, + 84B8AA8529F910AD00347A37 /* WalletConnectStateError.swift in Sources */, 8814773E29B071AD001E98A1 /* ImageViewModelProtocol+Optional.swift in Sources */, 84E493252731C064000534F2 /* AssetListViewModel.swift in Sources */, AEA0C8B8267C905500F9666F /* SelectedValidatorCell.swift in Sources */, @@ -19076,6 +19121,7 @@ 843A2C7126A85B5B00266F53 /* GenericTitleValueView.swift in Sources */, 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, + 84B8AA8329F90E3E00347A37 /* WalletConnectStateProtocols.swift in Sources */, 88421064289BBD9100306F2C /* Currency.swift in Sources */, 921E4891E85C0DC6FDD8A0D0 /* CrowdloanContributionConfirmInteractor.swift in Sources */, 88A6BCFF28CA15400047E4C2 /* LocksBalanceViewModelFactory.swift in Sources */, @@ -19281,6 +19327,7 @@ F27AAD7BC84793FA63027F8C /* AssetsSettingsInteractor.swift in Sources */, 982696E7AA7ACDA0B5131F6D /* AssetsSettingsViewController.swift in Sources */, 54110818BE6218276B4C55AF /* AssetsSettingsViewLayout.swift in Sources */, + 84B8AA8129F9097D00347A37 /* WalletConnectTransport.swift in Sources */, 599CEA840B4E888B643AEB5E /* AssetsSettingsViewFactory.swift in Sources */, 04B85867D67D56994D99FF14 /* NftListProtocols.swift in Sources */, E9625CE429290F5504728D62 /* NftListWireframe.swift in Sources */, @@ -19776,6 +19823,7 @@ D4184799A74D5F9B28F95E80 /* DelegateVotedReferendaWireframe.swift in Sources */, 835AE15B1F58CB87F775272F /* DelegateVotedReferendaPresenter.swift in Sources */, FBB0A83A6F48E4E0EE15E11A /* DelegateVotedReferendaInteractor.swift in Sources */, + 84B8AA7F29F9064300347A37 /* WalletConnectStateSigning.swift in Sources */, 3D61F07E10ED522E389B2192 /* DelegateVotedReferendaViewController.swift in Sources */, C317BBF2F1815F0A8D937428 /* DelegateVotedReferendaViewLayout.swift in Sources */, 8846F72929D6BB5300B8B776 /* Data+base16.swift in Sources */, diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift new file mode 100644 index 0000000000..5e0668dda8 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift @@ -0,0 +1,9 @@ +// +// WCStateAuthorizing.swift +// novawallet +// +// Created by Ruslan Rezin on 26.04.2023. +// Copyright © 2023 Nova Foundation. All rights reserved. +// + +import Foundation diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift new file mode 100644 index 0000000000..5be48d65a3 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift @@ -0,0 +1,5 @@ +import Foundation + +enum WalletConnectStateError: Error { + case unexpectedMessage(AnyObject, WalletConnectStateProtocol) +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift new file mode 100644 index 0000000000..bd94ded5ac --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift @@ -0,0 +1,9 @@ +// +// WCStateInitiating.swift +// novawallet +// +// Created by Ruslan Rezin on 26.04.2023. +// Copyright © 2023 Nova Foundation. All rights reserved. +// + +import Foundation diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift new file mode 100644 index 0000000000..6627b4be3b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift @@ -0,0 +1,7 @@ +import Foundation +import WalletConnectSwiftV2 + +enum WalletConnectStateMessage { + case proposal(Session.Proposal) + case request(Request) +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift new file mode 100644 index 0000000000..1b238da3be --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift @@ -0,0 +1,23 @@ +import Foundation +import WalletConnectSwiftV2 + +protocol WalletConnectStateMachineProtocol: AnyObject { + func emit(nextState: WalletConnectStateProtocol) + func emit(authRequest: DAppAuthRequest, nextState: WalletConnectStateProtocol) + func emit( + signingRequest: DAppOperationRequest, + type: DAppSigningType, + nextState: WalletConnectStateProtocol + ) + func emit(error: Error, nextState: WalletConnectStateProtocol) +} + +protocol WalletConnectStateProtocol: AnyObject { + var stateMachine: WalletConnectStateMachineProtocol? { get set } + + func canHandleMessage() -> Bool + + func handle(message: WalletConnectStateMessage, dataSource: DAppBrowserStateDataSource) + func handleOperation(response: DAppOperationResponse, dataSource: DAppBrowserStateDataSource) + func handleAuth(response: DAppAuthResponse, dataSource: DAppBrowserStateDataSource) +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift new file mode 100644 index 0000000000..4fc3c267bb --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift @@ -0,0 +1,9 @@ +// +// WCStateReady.swift +// novawallet +// +// Created by Ruslan Rezin on 26.04.2023. +// Copyright © 2023 Nova Foundation. All rights reserved. +// + +import Foundation diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift new file mode 100644 index 0000000000..6dbc800ca3 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -0,0 +1,9 @@ +// +// WCStateSigning.swift +// novawallet +// +// Created by Ruslan Rezin on 26.04.2023. +// Copyright © 2023 Nova Foundation. All rights reserved. +// + +import Foundation diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift new file mode 100644 index 0000000000..0ab5c3da56 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -0,0 +1,45 @@ +import Foundation + +final class WalletConnectTransport { + let service: WalletConnectServiceProtocol + let dataSource: DAppStateDataSource + + private var state: WalletConnectStateProtocol? + + init(service: WalletConnectServiceProtocol, dataSource: DAppStateDataSource) { + self.service = service + self.dataSource = dataSource + } +} + +extension WalletConnectTransport: DAppTransportProtocol { + var name: String { DAppTransports.walletConnect } + + func isIdle() -> Bool { + state?.canHandleMessage() ?? false + } + + func bringPhishingDetectedStateIfNeeded() -> Bool { + + } + + func process(message: Any, host: String) { + guard let message = message as? WalletConnectStateMessage else { + return + } + + state?.handle(message: message, dataSource: dataSource) + } + + func processConfirmation(response: DAppOperationResponse) { + state?.handleOperation(response: response, dataSource: dataSource) + } + + func processAuth(response: DAppAuthResponse) { + state?.handleAuth(response: response, dataSource: dataSource) + } + + func processChainsChanges() { + + } +} From 4aede58f03571d3fc388cb0ffb038124be50527a Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 27 Apr 2023 17:51:23 +0500 Subject: [PATCH 09/54] add auth message mapping --- novawallet.xcodeproj/project.pbxproj | 28 ++- .../WalletConnectProposalDecision.swift | 7 + .../WalletConnect/WalletConnectService.swift | 24 +++ .../DAppInteractionFactory.swift | 2 +- .../DAppInteractionMediator.swift | 35 ++-- .../DAppInteractionProtocols.swift | 9 +- .../Model/DAppStateDataSource.swift | 3 + .../Model/WalletConnectModelFactory.swift | 174 ++++++++++++++++++ .../Model/WalletConnectTransportMessage.swift | 16 ++ .../States/WalletConnectBaseState.swift | 16 ++ .../WalletConnectStateAuthorizing.swift | 65 ++++++- .../States/WalletConnectStateError.swift | 2 +- .../States/WalletConnectStateInitiating.swift | 38 +++- .../States/WalletConnectStateMessage.swift | 7 - .../States/WalletConnectStateNewMessage.swift | 87 +++++++++ .../States/WalletConnectStateProtocols.swift | 10 +- .../States/WalletConnectStateReady.swift | 39 +++- .../Transport/WalletConnectTransport.swift | 126 ++++++++++++- .../WalletConnectTransportError.swift | 6 + .../WalletConnectInteractor.swift | 46 +++-- .../WalletConnectViewFactory.swift | 18 +- 21 files changed, 670 insertions(+), 88 deletions(-) create mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectBaseState.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 13eeeb32e1..3dbd888820 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -512,6 +512,8 @@ 840DFF532894189D001B11EA /* ChainAddressDetailsMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF522894189D001B11EA /* ChainAddressDetailsMeasurement.swift */; }; 840DFF5528942CA9001B11EA /* ChainAddressDetailsPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF5428942CA9001B11EA /* ChainAddressDetailsPresentable.swift */; }; 840DFF57289482F5001B11EA /* ActionsManagePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF56289482F5001B11EA /* ActionsManagePresentable.swift */; }; + 840EAE6729FA8F0000453C7E /* WalletConnectProposalDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */; }; + 840EAE6929FA935900453C7E /* WalletConnectModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */; }; 84100F3626A6069200A5054E /* IconTitleValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3526A6069200A5054E /* IconTitleValueView.swift */; }; 84100F3826A6085C00A5054E /* YourValidatorListDescSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3726A6085C00A5054E /* YourValidatorListDescSectionView.swift */; }; 84100F3A26A60B0900A5054E /* YourValidatorListStatusSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3926A60B0900A5054E /* YourValidatorListStatusSectionView.swift */; }; @@ -1998,7 +2000,10 @@ 84B8AA8129F9097D00347A37 /* WalletConnectTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */; }; 84B8AA8329F90E3E00347A37 /* WalletConnectStateProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */; }; 84B8AA8529F910AD00347A37 /* WalletConnectStateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */; }; - 84B8AA8729F9115400347A37 /* WalletConnectStateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */; }; + 84B8AA8729F9115400347A37 /* WalletConnectTransportMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */; }; + 84B8AA8929FA2FE500347A37 /* WalletConnectTransportError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8829FA2FE500347A37 /* WalletConnectTransportError.swift */; }; + 84B8AA8B29FA3B0500347A37 /* WalletConnectBaseState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8A29FA3B0500347A37 /* WalletConnectBaseState.swift */; }; + 84B8AA8D29FA41F600347A37 /* WalletConnectStateNewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8AA8C29FA41F600347A37 /* WalletConnectStateNewMessage.swift */; }; 84B91AED287078B0001A0420 /* StorageProviderSourceFallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */; }; 84BA1F7E27CE23C1000AAB82 /* NftChainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */; }; 84BAB6102642C286007782D0 /* SelectedRebondVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */; }; @@ -3929,6 +3934,8 @@ 840DFF522894189D001B11EA /* ChainAddressDetailsMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsMeasurement.swift; sourceTree = ""; }; 840DFF5428942CA9001B11EA /* ChainAddressDetailsPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsPresentable.swift; sourceTree = ""; }; 840DFF56289482F5001B11EA /* ActionsManagePresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionsManagePresentable.swift; sourceTree = ""; }; + 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectProposalDecision.swift; sourceTree = ""; }; + 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectModelFactory.swift; sourceTree = ""; }; 84100F3526A6069200A5054E /* IconTitleValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconTitleValueView.swift; sourceTree = ""; }; 84100F3726A6085C00A5054E /* YourValidatorListDescSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourValidatorListDescSectionView.swift; sourceTree = ""; }; 84100F3926A60B0900A5054E /* YourValidatorListStatusSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourValidatorListStatusSectionView.swift; sourceTree = ""; }; @@ -5434,7 +5441,10 @@ 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectTransport.swift; sourceTree = ""; }; 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateProtocols.swift; sourceTree = ""; }; 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateError.swift; sourceTree = ""; }; - 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateMessage.swift; sourceTree = ""; }; + 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectTransportMessage.swift; sourceTree = ""; }; + 84B8AA8829FA2FE500347A37 /* WalletConnectTransportError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectTransportError.swift; sourceTree = ""; }; + 84B8AA8A29FA3B0500347A37 /* WalletConnectBaseState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectBaseState.swift; sourceTree = ""; }; + 84B8AA8C29FA41F600347A37 /* WalletConnectStateNewMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectStateNewMessage.swift; sourceTree = ""; }; 84B91AEC287078B0001A0420 /* StorageProviderSourceFallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageProviderSourceFallback.swift; sourceTree = ""; }; 84BA1F7D27CE23C1000AAB82 /* NftChainModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftChainModel.swift; sourceTree = ""; }; 84BAB60F2642C286007782D0 /* SelectedRebondVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedRebondVariant.swift; sourceTree = ""; }; @@ -10625,6 +10635,7 @@ children = ( 8490111329E68FCB005D688B /* WalletConnectService.swift */, 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */, + 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */, ); path = WalletConnect; sourceTree = ""; @@ -11874,6 +11885,8 @@ isa = PBXGroup; children = ( 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */, + 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */, + 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */, ); path = Model; sourceTree = ""; @@ -12848,6 +12861,7 @@ isa = PBXGroup; children = ( 84B8AA8029F9097D00347A37 /* WalletConnectTransport.swift */, + 84B8AA8829FA2FE500347A37 /* WalletConnectTransportError.swift */, ); path = Transport; sourceTree = ""; @@ -12861,7 +12875,8 @@ 84B8AA7E29F9064300347A37 /* WalletConnectStateSigning.swift */, 84B8AA8229F90E3E00347A37 /* WalletConnectStateProtocols.swift */, 84B8AA8429F910AD00347A37 /* WalletConnectStateError.swift */, - 84B8AA8629F9115400347A37 /* WalletConnectStateMessage.swift */, + 84B8AA8A29FA3B0500347A37 /* WalletConnectBaseState.swift */, + 84B8AA8C29FA41F600347A37 /* WalletConnectStateNewMessage.swift */, ); path = States; sourceTree = ""; @@ -17341,6 +17356,7 @@ 849014C224AA87E4008F705E /* LocalAuthPresenter.swift in Sources */, 88BB21A028D34C660019C6B4 /* DataProviderChange+Identifier.swift in Sources */, 8446F5F6281916D300B7A86C /* StakingRewardsHeaderCell.swift in Sources */, + 84B8AA8929FA2FE500347A37 /* WalletConnectTransportError.swift in Sources */, 84CA68D126BE99ED003B9453 /* RuntimeProviderFactory.swift in Sources */, 848F8B1928635A5600204BC4 /* TransferSetupPresenter.swift in Sources */, 841221A028F051EE00715C82 /* GovMetadataLocalStorageSubscriber.swift in Sources */, @@ -17457,7 +17473,7 @@ 840D92A7278EE8690007B979 /* DAppSignBytesConfirmInteractor.swift in Sources */, 84EC2D1A276C6600009B0BE1 /* PolkadotExtensionExtrinsic.swift in Sources */, 84A8FD8E265FDA76002ADB58 /* CrowdloanContributionConfirmData.swift in Sources */, - 84B8AA8729F9115400347A37 /* WalletConnectStateMessage.swift in Sources */, + 84B8AA8729F9115400347A37 /* WalletConnectTransportMessage.swift in Sources */, 84F98D9625E3E2B10040418E /* InMemoryDataProviderRepository.swift in Sources */, 8446F5FA28192FF500B7A86C /* ListLoadingView.swift in Sources */, 844384AC28538D3000611CE2 /* RewardCalculatorEngine.swift in Sources */, @@ -18616,6 +18632,7 @@ 845CB6FA26276326005F798B /* LongrunOperation.swift in Sources */, 8499FEC627BEC68000712589 /* UniquesInstanceMetadata.swift in Sources */, 84364D5C252FB65C00281F9A /* AssetDetailsContainingViewFactory.swift in Sources */, + 840EAE6729FA8F0000453C7E /* WalletConnectProposalDecision.swift in Sources */, 84EBFCE9285E7C100006327E /* XcmInstruction.swift in Sources */, 2AFF4BA2274D1E5C00D790B4 /* UsernameSetupViewLayout.swift in Sources */, 2A66CF4F25D109780006E4C1 /* CDPhishingItem+CoreDataDecodable.swift in Sources */, @@ -18723,6 +18740,7 @@ 844DBC67274E2B6E009F8351 /* AccountImportMnemonicView.swift in Sources */, 6D6C6FD2F13603BCE83CFC65 /* ExportMnemonicConfirmInteractor.swift in Sources */, 0CA307BC2F570941CD22C9AA /* ExportMnemonicConfirmViewFactory.swift in Sources */, + 840EAE6929FA935900453C7E /* WalletConnectModelFactory.swift in Sources */, 844CB57A26FA706C00396E13 /* ChainAssetDisplayInfo.swift in Sources */, 848DAEF7282274E700D56F55 /* ParachainStakingRemoteSubscriptionService.swift in Sources */, 845B821F26EF8E8900D25C72 /* ManagedMetaAccountModel.swift in Sources */, @@ -19178,6 +19196,7 @@ 2932BD7922FDCD64F4E9A57D /* AssetListPresenter.swift in Sources */, 84355CF628B63D19004E5C5E /* LedgerCrypto+Conversion.swift in Sources */, 8463781F2979DACB0034162B /* ReferendumsActivityViewModelFactory.swift in Sources */, + 84B8AA8B29FA3B0500347A37 /* WalletConnectBaseState.swift in Sources */, 84117074285B0E92006F4DFB /* XcmChain.swift in Sources */, 844D2A42281B24510049CF5E /* StackUrlCell.swift in Sources */, 98DADEB52480817D191188C1 /* AssetListInteractor.swift in Sources */, @@ -19757,6 +19776,7 @@ 62649D3FB6AACB508872C67A /* GovernanceUnlockConfirmInteractor.swift in Sources */, 6D603098CCF0B65AA726AD38 /* GovernanceUnlockConfirmViewController.swift in Sources */, 7DB7E81CC2F880E4736DE062 /* GovernanceUnlockConfirmViewLayout.swift in Sources */, + 84B8AA8D29FA41F600347A37 /* WalletConnectStateNewMessage.swift in Sources */, D5BB3A36DB9ADD25EE43109F /* GovernanceUnlockConfirmViewFactory.swift in Sources */, 58F385F41D42CC96373EDA42 /* TokensManageProtocols.swift in Sources */, CA3C4729115D875D0C80A3E8 /* TokensManageWireframe.swift in Sources */, diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift b/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift new file mode 100644 index 0000000000..a19abe78e9 --- /dev/null +++ b/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift @@ -0,0 +1,7 @@ +import Foundation +import WalletConnectSwiftV2 + +enum WalletConnectProposalDecision { + case approve(proposal: Session.Proposal, namespaces: [String: SessionNamespace]) + case reject(proposal: Session.Proposal) +} diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index 3f31791a05..da06e366a7 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -14,11 +14,14 @@ protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { var delegate: WalletConnectServiceDelegate? { get set } func connect(uri: String) + + func submit(proposalDecision: WalletConnectProposalDecision) } enum WalletConnectServiceError: Error { case setupNeeded case connectFailed(uri: String, internalError: Error) + case proposalFailed(decision: WalletConnectProposalDecision, internalError: Error) } final class WalletConnectService { @@ -169,6 +172,27 @@ extension WalletConnectService: WalletConnectServiceProtocol { } } + func submit(proposalDecision: WalletConnectProposalDecision) { + guard let client = client else { + notify(error: .setupNeeded) + return + } + + Task { [weak self] in + do { + switch proposalDecision { + case let .approve(proposal, namespaces): + try await client.approve(proposalId: proposal.id, namespaces: namespaces) + case let .reject(proposal): + try await client.reject(proposalId: proposal.id, reason: .userRejected) + } + } catch { + self?.logger.error("Decision submission failed: \(error)") + self?.notify(error: .proposalFailed(decision: proposalDecision, internalError: error)) + } + } + } + func setup() { setupNetworking() setupPairing() diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift index 63fbde1050..e73a48950e 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift @@ -1,7 +1,7 @@ import Foundation final class DAppInteractionFactory { - func createMediator() -> DAppInteractionMediating { + static func createMediator() -> DAppInteractionMediating { let logger = Logger.shared let presenter = DAppInteractionPresenter(logger: logger) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift index b9ae50fbdf..7ffb91ef03 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift @@ -3,7 +3,7 @@ import RobinHood final class DAppInteractionMediator { struct QueueMessage { - let host: String + let host: String? let transportName: String let underliningMessage: Any } @@ -64,7 +64,12 @@ final class DAppInteractionMediator { } } - private func verifyPhishing(for host: String, completion: ((Bool) -> Void)?) { + private func verifyPhishing(for host: String?, completion: ((Bool) -> Void)?) { + guard let host = host else { + completion?(true) + return + } + sequentialPhishingVerifier.verify(host: host) { [weak self] result in switch result { case let .success(isNotPhishing): @@ -82,30 +87,22 @@ final class DAppInteractionMediator { extension DAppInteractionMediator: DAppInteractionMediating { func register(transport: DAppTransportProtocol) { - securedLayer.scheduleExecutionIfAuthorized { [weak self] in - guard let self = self else { - return - } + guard !transports.contains(where: { $0 !== transport }) else { + return + } - guard !self.transports.contains(where: { $0 !== transport }) else { - return - } + transports.append(transport) - self.transports.append(transport) - } + transport.start() } func unregister(transport: DAppTransportProtocol) { - securedLayer.scheduleExecutionIfAuthorized { [weak self] in - guard let self = self else { - return - } + transports = transports.filter { $0 !== transport } - self.transports = self.transports.filter { $0 !== transport } - } + transport.stop() } - func process(message: Any, host: String, transport name: String) { + func process(message: Any, host: String?, transport name: String) { securedLayer.scheduleExecutionIfAuthorized { [weak self] in self?.logger?.debug("Did receive \(name) message from \(host): \(message)") @@ -157,7 +154,7 @@ extension DAppInteractionMediator: ChainsStoreDelegate { } } -extension DAppInteractionMediator: ApplicationServiceProtocol { +extension DAppInteractionMediator { func setup() { chainsStore.delegate = self chainsStore.setup() diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift index 0f516d13dc..716d783760 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift @@ -6,13 +6,16 @@ protocol DAppTransportProtocol: AnyObject { func isIdle() -> Bool func bringPhishingDetectedStateIfNeeded() -> Bool - func process(message: Any, host: String) + func process(message: Any, host: String?) func processConfirmation(response: DAppOperationResponse) func processAuth(response: DAppAuthResponse) func processChainsChanges() + + func start() + func stop() } -protocol DAppInteractionMediating { +protocol DAppInteractionMediating: ApplicationServiceProtocol { var chainsStore: ChainsStoreProtocol { get } var settingsRepository: AnyDataProviderRepository { get } var operationQueue: OperationQueue { get } @@ -20,7 +23,7 @@ protocol DAppInteractionMediating { func register(transport: DAppTransportProtocol) func unregister(transport: DAppTransportProtocol) - func process(message: Any, host: String, transport name: String) + func process(message: Any, host: String?, transport name: String) func process(authRequest: DAppAuthRequest) func process(signingRequest: DAppOperationRequest, type: DAppSigningType) } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift b/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift index 032f118ffb..c5879f2088 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift @@ -5,14 +5,17 @@ final class DAppStateDataSource { let chainsStore: ChainsStoreProtocol let dAppSettingsRepository: AnyDataProviderRepository let operationQueue: OperationQueue + let walletSettings: SelectedWalletSettings init( chainsStore: ChainsStoreProtocol, dAppSettingsRepository: AnyDataProviderRepository, + walletSettings: SelectedWalletSettings, operationQueue: OperationQueue ) { self.chainsStore = chainsStore self.dAppSettingsRepository = dAppSettingsRepository + self.walletSettings = walletSettings self.operationQueue = operationQueue } } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift new file mode 100644 index 0000000000..e6e20b992e --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift @@ -0,0 +1,174 @@ +import Foundation +import WalletConnectSwiftV2 + +struct WalletConnectChainsResolution { + let resolved: [Blockchain: ChainModel] + let unresolved: Set + + init(resolved: [Blockchain: ChainModel] = [:], unresolved: Set = []) { + self.resolved = resolved + self.unresolved = unresolved + } + + func adding(chain: ChainModel, blockchain: Blockchain) -> WalletConnectChainsResolution { + var newResolved = resolved + newResolved[blockchain] = chain + + return .init(resolved: newResolved, unresolved: unresolved) + } + + func adding(unresolvedId: String) -> WalletConnectChainsResolution { + let newUnresolved = unresolved.union([unresolvedId]) + return .init(resolved: resolved, unresolved: newUnresolved) + } + + func merging(with resolution: WalletConnectChainsResolution) -> WalletConnectChainsResolution { + let newResolved = resolved.merging(resolution.resolved) { value1, _ in + value1 + } + + let newUnresolved = unresolved.union(resolution.unresolved) + + return .init(resolved: newResolved, unresolved: newUnresolved) + } +} + +struct WalletConnectProposalResolution { + let requiredNamespaces: WalletConnectChainsResolution + let optionalNamespaces: WalletConnectChainsResolution? + + func allResolvedChains() -> WalletConnectChainsResolution { + if let optionalNamespaces = optionalNamespaces { + return requiredNamespaces.merging(with: optionalNamespaces) + } else { + return requiredNamespaces + } + } +} + +enum WalletConnectModelFactory { + private static func createSessionNamespaces( + using proposalNamespaces: [String: ProposalNamespace], + wallet: MetaAccountModel, + resolvedChains: [Blockchain: ChainModel] + ) -> [String: SessionNamespace] { + proposalNamespaces.reduce(into: [:]) { accum, keyValue in + let namespaceId = keyValue.key + let proposalNamespace = keyValue.value + + let accounts: [Account]? = proposalNamespace.chains?.compactMap { blockchain in + guard + let chain = resolvedChains[blockchain], + let account = wallet.fetch(for: chain.accountRequest()), + let address = account.toAddress() else { + return nil + } + + return Account(blockchain: blockchain, address: address) + } + + let blockchains = accounts?.map(\.blockchain) + + let sessionNamespace = SessionNamespace( + chains: blockchains.map { Set($0) }, + accounts: Set(accounts ?? []), + methods: proposalNamespace.methods, + events: proposalNamespace.events + ) + + accum[namespaceId] = sessionNamespace + } + } + + private static func resolveChains( + from blockchains: Set, + chainsStore: ChainsStoreProtocol + ) -> WalletConnectChainsResolution { + let knownChainIds = chainsStore.availableChainIds() + + return blockchains.reduce(WalletConnectChainsResolution()) { result, blockchain in + do { + let caip2ChainId = try Caip2.ChainId(raw: blockchain.absoluteString) + + if + let chainId = knownChainIds.first(where: { caip2ChainId.match($0) }), + let chain = chainsStore.getChain(for: chainId) { + return result.adding(chain: chain, blockchain: blockchain) + } else { + return result.adding(unresolvedId: blockchain.absoluteString) + } + + } catch { + return result.adding(unresolvedId: blockchain.absoluteString) + } + } + } +} + +extension WalletConnectModelFactory { + static func createSessionNamespaces( + from proposal: Session.Proposal, + wallet: MetaAccountModel, + resolvedChains: [Blockchain: ChainModel] + ) -> [String: SessionNamespace] { + let requiredNamespaces = createSessionNamespaces( + using: proposal.requiredNamespaces, + wallet: wallet, + resolvedChains: resolvedChains + ) + + let optionalNamespaces = createSessionNamespaces( + using: proposal.optionalNamespaces ?? [:], + wallet: wallet, + resolvedChains: resolvedChains + ) + + return requiredNamespaces.merging(optionalNamespaces) { namespace1, namespace2 in + let blockchains: Set? + + if namespace1.chains != nil || namespace2.chains != nil { + blockchains = (namespace1.chains ?? []).union(namespace2.chains ?? []) + } else { + blockchains = nil + } + + return SessionNamespace( + chains: blockchains, + accounts: namespace1.accounts.union(namespace2.accounts), + methods: namespace1.methods.union(namespace2.methods), + events: namespace1.events.union(namespace2.events) + ) + } + } + + static func createChainsResolution( + from proposalNamespaces: [String: ProposalNamespace], + chainsStore: ChainsStoreProtocol + ) -> WalletConnectChainsResolution { + proposalNamespaces.values.reduce(WalletConnectChainsResolution()) { accum, namespace in + guard let chains = namespace.chains else { + return accum + } + + let newResolution = resolveChains(from: chains, chainsStore: chainsStore) + + return accum.merging(with: newResolution) + } + } + + static func createProposalResolution( + from proposal: Session.Proposal, + chainsStore: ChainsStoreProtocol + ) -> WalletConnectProposalResolution { + let requiredNamespaces = createChainsResolution( + from: proposal.requiredNamespaces, + chainsStore: chainsStore + ) + + let optionalNamespaces = proposal.optionalNamespaces.map { + createChainsResolution(from: $0, chainsStore: chainsStore) + } + + return .init(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift new file mode 100644 index 0000000000..98ce28f142 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift @@ -0,0 +1,16 @@ +import Foundation +import WalletConnectSwiftV2 + +enum WalletConnectTransportMessage { + case proposal(Session.Proposal) + case request(Request) + + var host: String? { + switch self { + case let .proposal(proposal): + return URL(string: proposal.proposer.url)?.host + case .request: + return nil + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectBaseState.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectBaseState.swift new file mode 100644 index 0000000000..1bea9dd250 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectBaseState.swift @@ -0,0 +1,16 @@ +import Foundation + +class WalletConnectBaseState { + weak var stateMachine: WalletConnectStateMachineProtocol? + + init(stateMachine: WalletConnectStateMachineProtocol) { + self.stateMachine = stateMachine + } + + func emitUnexpected(message: Any, nextState: WalletConnectStateProtocol) { + stateMachine?.emit( + error: .unexpectedMessage(message, nextState), + nextState: nextState + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift index 5e0668dda8..138b0d0dad 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift @@ -1,9 +1,58 @@ -// -// WCStateAuthorizing.swift -// novawallet -// -// Created by Ruslan Rezin on 26.04.2023. -// Copyright © 2023 Nova Foundation. All rights reserved. -// - import Foundation +import WalletConnectSwiftV2 + +class WalletConnectStateAuthorizing: WalletConnectBaseState { + let proposal: Session.Proposal + let resolution: WalletConnectProposalResolution + + init( + proposal: Session.Proposal, + resolution: WalletConnectProposalResolution, + stateMachine: WalletConnectStateMachineProtocol + ) { + self.proposal = proposal + self.resolution = resolution + + super.init(stateMachine: stateMachine) + } +} + +extension WalletConnectStateAuthorizing: WalletConnectStateProtocol { + func canHandleMessage() -> Bool { + false + } + + func handle(message: WalletConnectTransportMessage, dataSource _: DAppStateDataSource) { + emitUnexpected(message: message, nextState: self) + } + + func handleOperation(response: DAppOperationResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + + let nextState = WalletConnectStateReady(stateMachine: stateMachine) + + guard response.approved else { + stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) + return + } + + let namespaces = WalletConnectModelFactory.createSessionNamespaces( + from: proposal, + wallet: response.wallet, + resolvedChains: resolution.allResolvedChains().resolved + ) + + stateMachine.emit( + proposalDecision: .approve(proposal: proposal, namespaces: namespaces), + nextState: nextState + ) + } + + func proceed(with _: DAppStateDataSource) {} +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift index 5be48d65a3..9e2a2fb089 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift @@ -1,5 +1,5 @@ import Foundation enum WalletConnectStateError: Error { - case unexpectedMessage(AnyObject, WalletConnectStateProtocol) + case unexpectedMessage(Any, WalletConnectStateProtocol) } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift index bd94ded5ac..df3cf9e2b7 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift @@ -1,9 +1,31 @@ -// -// WCStateInitiating.swift -// novawallet -// -// Created by Ruslan Rezin on 26.04.2023. -// Copyright © 2023 Nova Foundation. All rights reserved. -// - import Foundation + +final class WalletConnectStateInitiating: WalletConnectBaseState {} + +extension WalletConnectStateInitiating: WalletConnectStateProtocol { + func canHandleMessage() -> Bool { + false + } + + func handle(message: WalletConnectTransportMessage, dataSource _: DAppStateDataSource) { + emitUnexpected(message: message, nextState: self) + } + + func handleOperation(response: DAppOperationResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func proceed(with dataSource: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + + if !dataSource.chainsStore.availableChainIds().isEmpty { + stateMachine.emit(nextState: WalletConnectStateReady(stateMachine: stateMachine)) + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift deleted file mode 100644 index 6627b4be3b..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateMessage.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation -import WalletConnectSwiftV2 - -enum WalletConnectStateMessage { - case proposal(Session.Proposal) - case request(Request) -} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift new file mode 100644 index 0000000000..f1403a37f4 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -0,0 +1,87 @@ +import Foundation +import WalletConnectSwiftV2 + +final class WalletConnectStateNewMessage: WalletConnectBaseState { + struct ResolutionResult { + let resolved: [Blockchain: ChainModel] + let unresolved: Set + + func adding(chain: ChainModel, blockchain: Blockchain) -> ResolutionResult { + var newResolved = resolved + newResolved[blockchain] = chain + + return .init(resolved: newResolved, unresolved: unresolved) + } + + func adding(unresolvedId: String) -> ResolutionResult { + let newUnresolved = unresolved.union([unresolvedId]) + return .init(resolved: resolved, unresolved: newUnresolved) + } + } + + let message: WalletConnectTransportMessage + + init( + message: WalletConnectTransportMessage, + stateMachine: WalletConnectStateMachineProtocol + ) { + self.message = message + + super.init(stateMachine: stateMachine) + } + + private func process(proposal: Session.Proposal, dataSource: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + + let resolution = WalletConnectModelFactory.createProposalResolution( + from: proposal, + chainsStore: dataSource.chainsStore + ) + + let requiredChains = Set(resolution.requiredNamespaces.resolved.values) + let optionalChains = resolution.optionalNamespaces.map { Set($0.resolved.values) } + let unresolvedChains = resolution.requiredNamespaces.unresolved.union( + resolution.optionalNamespaces?.unresolved ?? [] + ) + + let authRequest = DAppAuthRequest( + transportName: DAppTransports.walletConnect, + identifier: proposal.pairingTopic, + wallet: dataSource.walletSettings.value, + origin: proposal.proposer.url, + dApp: proposal.proposer.name, + dAppIcon: proposal.proposer.icons.first.flatMap { URL(string: $0) }, + requiredChains: requiredChains, + optionalChains: optionalChains, + unknownChains: unresolvedChains + ) + + let nextState = WalletConnectStateAuthorizing( + proposal: proposal, + resolution: resolution, + stateMachine: stateMachine + ) + + stateMachine.emit(authRequest: authRequest, nextState: nextState) + } +} + +extension WalletConnectStateNewMessage: WalletConnectStateProtocol { + func canHandleMessage() -> Bool { + false + } + + func handle(message _: WalletConnectTransportMessage, dataSource _: DAppStateDataSource) {} + + func handleOperation(response: DAppOperationResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func proceed(with _: DAppStateDataSource) {} +} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift index 1b238da3be..85c9568b8d 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift @@ -9,7 +9,8 @@ protocol WalletConnectStateMachineProtocol: AnyObject { type: DAppSigningType, nextState: WalletConnectStateProtocol ) - func emit(error: Error, nextState: WalletConnectStateProtocol) + func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) + func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) } protocol WalletConnectStateProtocol: AnyObject { @@ -17,7 +18,8 @@ protocol WalletConnectStateProtocol: AnyObject { func canHandleMessage() -> Bool - func handle(message: WalletConnectStateMessage, dataSource: DAppBrowserStateDataSource) - func handleOperation(response: DAppOperationResponse, dataSource: DAppBrowserStateDataSource) - func handleAuth(response: DAppAuthResponse, dataSource: DAppBrowserStateDataSource) + func handle(message: WalletConnectTransportMessage, dataSource: DAppStateDataSource) + func handleOperation(response: DAppOperationResponse, dataSource: DAppStateDataSource) + func handleAuth(response: DAppAuthResponse, dataSource: DAppStateDataSource) + func proceed(with dataSource: DAppStateDataSource) } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift index 4fc3c267bb..68748eeb43 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateReady.swift @@ -1,9 +1,32 @@ -// -// WCStateReady.swift -// novawallet -// -// Created by Ruslan Rezin on 26.04.2023. -// Copyright © 2023 Nova Foundation. All rights reserved. -// - import Foundation + +final class WalletConnectStateReady: WalletConnectBaseState {} + +extension WalletConnectStateReady: WalletConnectStateProtocol { + func canHandleMessage() -> Bool { + true + } + + func handle(message: WalletConnectTransportMessage, dataSource _: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + + let nextState = WalletConnectStateNewMessage( + message: message, + stateMachine: stateMachine + ) + + stateMachine.emit(nextState: nextState) + } + + func handleOperation(response: DAppOperationResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func proceed(with _: DAppStateDataSource) {} +} diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 0ab5c3da56..e8f2c32e67 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -1,18 +1,56 @@ import Foundation +import WalletConnectSwiftV2 + +protocol WalletConnectTransportProtocol: DAppTransportProtocol { + var delegate: WalletConnectTransportDelegate? { get set } + + func connect(uri: String) +} + +protocol WalletConnectTransportDelegate: AnyObject { + func walletConnect( + transport: WalletConnectTransportProtocol, + didReceive message: WalletConnectTransportMessage + ) + + func walletConnect(transport: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) + + func walletConnect( + transport: WalletConnectTransportProtocol, + sign request: DAppOperationRequest, + type: DAppSigningType + ) + + func walletConnect(transport: WalletConnectTransportProtocol, didFail error: WalletConnectTransportError) +} final class WalletConnectTransport { let service: WalletConnectServiceProtocol let dataSource: DAppStateDataSource + let logger: LoggerProtocol + + weak var delegate: WalletConnectTransportDelegate? private var state: WalletConnectStateProtocol? - init(service: WalletConnectServiceProtocol, dataSource: DAppStateDataSource) { + init( + service: WalletConnectServiceProtocol, + dataSource: DAppStateDataSource, + logger: LoggerProtocol + ) { self.service = service self.dataSource = dataSource + self.logger = logger + } +} + +extension WalletConnectTransport: WalletConnectTransportProtocol { + func connect(uri: String) { + service.connect(uri: uri) } } -extension WalletConnectTransport: DAppTransportProtocol { +extension WalletConnectTransport { var name: String { DAppTransports.walletConnect } func isIdle() -> Bool { @@ -20,11 +58,12 @@ extension WalletConnectTransport: DAppTransportProtocol { } func bringPhishingDetectedStateIfNeeded() -> Bool { - + // TODO: Handle phishing transition + true } - func process(message: Any, host: String) { - guard let message = message as? WalletConnectStateMessage else { + func process(message: Any, host _: String?) { + guard let message = message as? WalletConnectTransportMessage else { return } @@ -40,6 +79,83 @@ extension WalletConnectTransport: DAppTransportProtocol { } func processChainsChanges() { + state?.proceed(with: dataSource) + } + + func start() { + service.delegate = self + service.setup() + } + + func stop() { + service.throttle() + } +} + +extension WalletConnectTransport: WalletConnectStateMachineProtocol { + func emit(nextState: WalletConnectStateProtocol) { + state = nextState + + nextState.proceed(with: dataSource) + } + + func emit(authRequest: DAppAuthRequest, nextState: WalletConnectStateProtocol) { + state = nextState + + delegate?.walletConnect(transport: self, authorize: authRequest) + + nextState.proceed(with: dataSource) + } + + func emit( + signingRequest: DAppOperationRequest, + type: DAppSigningType, + nextState: WalletConnectStateProtocol + ) { + state = nextState + + delegate?.walletConnect(transport: self, sign: signingRequest, type: type) + + nextState.proceed(with: dataSource) + } + + func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) { + state = nextState + + service.submit(proposalDecision: proposalDecision) + + nextState.proceed(with: dataSource) + } + + func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) { + state = nextState + + delegate?.walletConnect(transport: self, didFail: .stateFailed(error)) + + nextState.proceed(with: dataSource) + } +} + +extension WalletConnectTransport: WalletConnectServiceDelegate { + func walletConnect(service _: WalletConnectServiceProtocol, proposal: Session.Proposal) { + logger.debug("Proposal: \(proposal)") + + delegate?.walletConnect(transport: self, didReceive: .proposal(proposal)) + } + + func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { + logger.debug("New session: \(establishedSession)") + } + + func walletConnect(service _: WalletConnectServiceProtocol, request: Request) { + logger.debug("New session: \(request)") + + delegate?.walletConnect(transport: self, didReceive: .request(request)) + } + + func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { + logger.error("Error: \(error)") + delegate?.walletConnect(transport: self, didFail: .serviceFailed(error)) } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift new file mode 100644 index 0000000000..be6f733f94 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift @@ -0,0 +1,6 @@ +import Foundation + +enum WalletConnectTransportError: Error { + case stateFailed(WalletConnectStateError) + case serviceFailed(WalletConnectServiceError) +} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift index b3b51f6933..f3af2be9b3 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift @@ -4,44 +4,52 @@ import WalletConnectSwiftV2 final class WalletConnectInteractor { weak var presenter: WalletConnectInteractorOutputProtocol? - let service: WalletConnectServiceProtocol - let logger: LoggerProtocol + let transport: WalletConnectTransportProtocol + let mediator: DAppInteractionMediating - init(service: WalletConnectServiceProtocol, logger: LoggerProtocol) { - self.service = service - self.logger = logger + init(mediator: DAppInteractionMediating, transport: WalletConnectTransportProtocol) { + self.mediator = mediator + self.transport = transport } deinit { - service.throttle() + mediator.unregister(transport: transport) } } extension WalletConnectInteractor: WalletConnectInteractorInputProtocol { func setup() { - service.delegate = self - service.setup() + transport.delegate = self + mediator.register(transport: transport) } func connect(uri: String) { - service.connect(uri: uri) + transport.connect(uri: uri) } } -extension WalletConnectInteractor: WalletConnectServiceDelegate { - func walletConnect(service _: WalletConnectServiceProtocol, proposal: Session.Proposal) { - logger.debug("Proposal: \(proposal)") +extension WalletConnectInteractor: WalletConnectTransportDelegate { + func walletConnect( + transport: WalletConnectTransportProtocol, + didReceive message: WalletConnectTransportMessage + ) { + mediator.process(message: message, host: message.host, transport: transport.name) } - func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { - logger.debug("New session: \(establishedSession)") - } + func walletConnect( + transport _: WalletConnectTransportProtocol, + didFail _: WalletConnectTransportError + ) {} - func walletConnect(service _: WalletConnectServiceProtocol, request: Request) { - logger.debug("New session: \(request)") + func walletConnect(transport _: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) { + mediator.process(authRequest: request) } - func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { - logger.error("Error: \(error)") + func walletConnect( + transport _: WalletConnectTransportProtocol, + sign request: DAppOperationRequest, + type: DAppSigningType + ) { + mediator.process(signingRequest: request, type: type) } } diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift index 12e80bf23d..3f3d490e30 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift @@ -23,6 +23,22 @@ struct WalletConnectViewFactory { let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) let service = WalletConnectService(metadata: metadata) - return .init(service: service, logger: Logger.shared) + let mediator = DAppInteractionFactory.createMediator() + mediator.setup() + + let dataSource = DAppStateDataSource( + chainsStore: mediator.chainsStore, + dAppSettingsRepository: mediator.settingsRepository, + walletSettings: SelectedWalletSettings.shared, + operationQueue: mediator.operationQueue + ) + + let transport = WalletConnectTransport( + service: service, + dataSource: dataSource, + logger: Logger.shared + ) + + return .init(mediator: mediator, transport: transport) } } From 1522aed8d594942c436bf24ffa4d4461df0e59dd Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 28 Apr 2023 09:43:37 +0500 Subject: [PATCH 10/54] authorizing request --- .../DAppInteractionMediator.swift | 13 ++++++- .../DAppInteractionProtocols.swift | 1 + .../States/WalletConnectStateInitiating.swift | 4 +- .../States/WalletConnectStateNewMessage.swift | 11 +++++- .../States/WalletConnectStateSigning.swift | 39 +++++++++++++++---- .../Transport/WalletConnectTransport.swift | 13 +++++++ .../WalletConnectInteractor.swift | 8 +++- .../WalletConnectPresenter.swift | 4 +- .../WalletConnectProtocols.swift | 1 + .../WalletConnectWireframe.swift | 4 ++ 10 files changed, 83 insertions(+), 15 deletions(-) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift index 7ffb91ef03..0ea3f79e88 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift @@ -44,6 +44,7 @@ final class DAppInteractionMediator { private func processMessageIfNeeded() { guard transports.allSatisfy({ $0.isIdle() }), let queueMessage = messageQueue.first else { + logger?.debug("Some of the transports busy and can't process messages: \(messageQueue.count)") return } @@ -104,7 +105,7 @@ extension DAppInteractionMediator: DAppInteractionMediating { func process(message: Any, host: String?, transport name: String) { securedLayer.scheduleExecutionIfAuthorized { [weak self] in - self?.logger?.debug("Did receive \(name) message from \(host): \(message)") + self?.logger?.debug("Did receive \(name) message from \(host ?? ""): \(message)") self?.verifyPhishing(for: host) { [weak self] isNotPhishing in if isNotPhishing { @@ -132,6 +133,12 @@ extension DAppInteractionMediator: DAppInteractionMediating { self?.presenter.didReceiveConfirmation(request: signingRequest, type: type) } } + + func processMessageQueue() { + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.processMessageIfNeeded() + } + } } extension DAppInteractionMediator: DAppInteractionInputProtocol { @@ -149,7 +156,9 @@ extension DAppInteractionMediator: DAppInteractionInputProtocol { } extension DAppInteractionMediator: ChainsStoreDelegate { - func didUpdateChainsStore(_: ChainsStoreProtocol) { + func didUpdateChainsStore(_ chainsStore: ChainsStoreProtocol) { + logger?.debug("Did update chain store: \(chainsStore.availableChainIds().count)") + transports.forEach { $0.processChainsChanges() } } } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift index 716d783760..8f8e0dbd1c 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift @@ -26,6 +26,7 @@ protocol DAppInteractionMediating: ApplicationServiceProtocol { func process(message: Any, host: String?, transport name: String) func process(authRequest: DAppAuthRequest) func process(signingRequest: DAppOperationRequest, type: DAppSigningType) + func processMessageQueue() } protocol DAppInteractionInputProtocol: AnyObject { diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift index df3cf9e2b7..7b3e7dfe27 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateInitiating.swift @@ -24,7 +24,9 @@ extension WalletConnectStateInitiating: WalletConnectStateProtocol { return } - if !dataSource.chainsStore.availableChainIds().isEmpty { + let chainIds = dataSource.chainsStore.availableChainIds() + + if !chainIds.isEmpty { stateMachine.emit(nextState: WalletConnectStateReady(stateMachine: stateMachine)) } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index f1403a37f4..bf799152ad 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -55,7 +55,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { dAppIcon: proposal.proposer.icons.first.flatMap { URL(string: $0) }, requiredChains: requiredChains, optionalChains: optionalChains, - unknownChains: unresolvedChains + unknownChains: !unresolvedChains.isEmpty ? unresolvedChains : nil ) let nextState = WalletConnectStateAuthorizing( @@ -83,5 +83,12 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { emitUnexpected(message: response, nextState: self) } - func proceed(with _: DAppStateDataSource) {} + func proceed(with dataSource: DAppStateDataSource) { + switch message { + case let .proposal(proposal): + process(proposal: proposal, dataSource: dataSource) + case .request: + break + } + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index 6dbc800ca3..bea6db9602 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -1,9 +1,32 @@ -// -// WCStateSigning.swift -// novawallet -// -// Created by Ruslan Rezin on 26.04.2023. -// Copyright © 2023 Nova Foundation. All rights reserved. -// - import Foundation +import WalletConnectSwiftV2 + +final class WalletConnectStateSigning: WalletConnectBaseState { + let request: Request + + init(request: Request, stateMachine: WalletConnectStateMachineProtocol) { + self.request = request + + super.init(stateMachine: stateMachine) + } +} + +extension WalletConnectStateSigning: WalletConnectStateProtocol { + func canHandleMessage() -> Bool { + false + } + + func handle(message: WalletConnectTransportMessage, dataSource: DAppStateDataSource) { + emitUnexpected(message: message, nextState: self) + } + + func handleOperation(response: DAppOperationResponse, dataSource: DAppStateDataSource) { + + } + + func handleAuth(response: DAppAuthResponse, dataSource: DAppStateDataSource) { + emitUnexpected(message: response, nextState: self) + } + + func proceed(with dataSource: DAppStateDataSource) {} +} diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index e8f2c32e67..8fcb70763e 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -22,6 +22,8 @@ protocol WalletConnectTransportDelegate: AnyObject { ) func walletConnect(transport: WalletConnectTransportProtocol, didFail error: WalletConnectTransportError) + + func walletConnectAskNextMessage(transport: WalletConnectTransportProtocol) } final class WalletConnectTransport { @@ -85,6 +87,9 @@ extension WalletConnectTransport { func start() { service.delegate = self service.setup() + + state = WalletConnectStateInitiating(stateMachine: self) + state?.proceed(with: dataSource) } func stop() { @@ -94,9 +99,15 @@ extension WalletConnectTransport { extension WalletConnectTransport: WalletConnectStateMachineProtocol { func emit(nextState: WalletConnectStateProtocol) { + let prevCanHandleMessage = state?.canHandleMessage() ?? false + state = nextState nextState.proceed(with: dataSource) + + if !prevCanHandleMessage, nextState.canHandleMessage() { + delegate?.walletConnectAskNextMessage(transport: self) + } } func emit(authRequest: DAppAuthRequest, nextState: WalletConnectStateProtocol) { @@ -145,6 +156,8 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { logger.debug("New session: \(establishedSession)") + + // TODO: Handle session } func walletConnect(service _: WalletConnectServiceProtocol, request: Request) { diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift index f3af2be9b3..30b73611b4 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift @@ -39,7 +39,9 @@ extension WalletConnectInteractor: WalletConnectTransportDelegate { func walletConnect( transport _: WalletConnectTransportProtocol, didFail _: WalletConnectTransportError - ) {} + ) { + // TODO: Handle error + } func walletConnect(transport _: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) { mediator.process(authRequest: request) @@ -52,4 +54,8 @@ extension WalletConnectInteractor: WalletConnectTransportDelegate { ) { mediator.process(signingRequest: request, type: type) } + + func walletConnectAskNextMessage(transport _: WalletConnectTransportProtocol) { + mediator.processMessageQueue() + } } diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift index 65027c72f7..09cb9bcadf 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift @@ -34,6 +34,8 @@ extension WalletConnectPresenter: URIScanDelegate { func uriScanDidReceive(uri: String, context _: AnyObject?) { logger.debug("Wallet Connect URI: \(uri)") - interactor.connect(uri: uri) + wireframe.hideUriScanAnimated(from: view) { [weak self] in + self?.interactor.connect(uri: uri) + } } } diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift index 96798510bc..1f5fd51507 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift @@ -14,4 +14,5 @@ protocol WalletConnectInteractorOutputProtocol: AnyObject {} protocol WalletConnectWireframeProtocol: AnyObject { func showScan(from view: WalletConnectViewProtocol?, delegate: URIScanDelegate) + func hideUriScanAnimated(from view: ControllerBackedProtocol?, completion: @escaping () -> Void) } diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift index d9914e14e9..bb58243286 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift +++ b/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift @@ -8,4 +8,8 @@ final class WalletConnectWireframe: WalletConnectWireframeProtocol { view?.controller.present(scanView.controller, animated: true) } + + func hideUriScanAnimated(from view: ControllerBackedProtocol?, completion: @escaping () -> Void) { + view?.controller.dismiss(animated: true, completion: completion) + } } From a4745fb7e7f0936d76c588e96263151d0b3d9818 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 28 Apr 2023 11:02:37 +0500 Subject: [PATCH 11/54] add signing logic --- novawallet.xcodeproj/project.pbxproj | 12 ++++++--- .../WalletConnect/WalletConnectDecision.swift | 26 +++++++++++++++++++ .../WalletConnectProposalDecision.swift | 7 ----- .../WalletConnect/WalletConnectService.swift | 22 ++++++++++++++++ .../Model/WalletConnectMethod.swift | 11 ++++++++ .../Model/WalletConnectModelFactory.swift | 5 ++++ .../States/WalletConnectStateNewMessage.swift | 20 ++++++++++++++ .../States/WalletConnectStateProtocols.swift | 1 + .../States/WalletConnectStateSigning.swift | 10 +++---- .../Transport/WalletConnectTransport.swift | 8 ++++++ 10 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectDecision.swift delete mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 3dbd888820..a196f1f465 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -512,8 +512,9 @@ 840DFF532894189D001B11EA /* ChainAddressDetailsMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF522894189D001B11EA /* ChainAddressDetailsMeasurement.swift */; }; 840DFF5528942CA9001B11EA /* ChainAddressDetailsPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF5428942CA9001B11EA /* ChainAddressDetailsPresentable.swift */; }; 840DFF57289482F5001B11EA /* ActionsManagePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840DFF56289482F5001B11EA /* ActionsManagePresentable.swift */; }; - 840EAE6729FA8F0000453C7E /* WalletConnectProposalDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */; }; + 840EAE6729FA8F0000453C7E /* WalletConnectDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6629FA8F0000453C7E /* WalletConnectDecision.swift */; }; 840EAE6929FA935900453C7E /* WalletConnectModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */; }; + 840EAE6B29FB8AEA00453C7E /* WalletConnectMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */; }; 84100F3626A6069200A5054E /* IconTitleValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3526A6069200A5054E /* IconTitleValueView.swift */; }; 84100F3826A6085C00A5054E /* YourValidatorListDescSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3726A6085C00A5054E /* YourValidatorListDescSectionView.swift */; }; 84100F3A26A60B0900A5054E /* YourValidatorListStatusSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84100F3926A60B0900A5054E /* YourValidatorListStatusSectionView.swift */; }; @@ -3934,8 +3935,9 @@ 840DFF522894189D001B11EA /* ChainAddressDetailsMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsMeasurement.swift; sourceTree = ""; }; 840DFF5428942CA9001B11EA /* ChainAddressDetailsPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsPresentable.swift; sourceTree = ""; }; 840DFF56289482F5001B11EA /* ActionsManagePresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionsManagePresentable.swift; sourceTree = ""; }; - 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectProposalDecision.swift; sourceTree = ""; }; + 840EAE6629FA8F0000453C7E /* WalletConnectDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectDecision.swift; sourceTree = ""; }; 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectModelFactory.swift; sourceTree = ""; }; + 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectMethod.swift; sourceTree = ""; }; 84100F3526A6069200A5054E /* IconTitleValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconTitleValueView.swift; sourceTree = ""; }; 84100F3726A6085C00A5054E /* YourValidatorListDescSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourValidatorListDescSectionView.swift; sourceTree = ""; }; 84100F3926A60B0900A5054E /* YourValidatorListStatusSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourValidatorListStatusSectionView.swift; sourceTree = ""; }; @@ -10635,7 +10637,7 @@ children = ( 8490111329E68FCB005D688B /* WalletConnectService.swift */, 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */, - 840EAE6629FA8F0000453C7E /* WalletConnectProposalDecision.swift */, + 840EAE6629FA8F0000453C7E /* WalletConnectDecision.swift */, ); path = WalletConnect; sourceTree = ""; @@ -11887,6 +11889,7 @@ 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */, 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */, 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */, + 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */, ); path = Model; sourceTree = ""; @@ -18632,7 +18635,7 @@ 845CB6FA26276326005F798B /* LongrunOperation.swift in Sources */, 8499FEC627BEC68000712589 /* UniquesInstanceMetadata.swift in Sources */, 84364D5C252FB65C00281F9A /* AssetDetailsContainingViewFactory.swift in Sources */, - 840EAE6729FA8F0000453C7E /* WalletConnectProposalDecision.swift in Sources */, + 840EAE6729FA8F0000453C7E /* WalletConnectDecision.swift in Sources */, 84EBFCE9285E7C100006327E /* XcmInstruction.swift in Sources */, 2AFF4BA2274D1E5C00D790B4 /* UsernameSetupViewLayout.swift in Sources */, 2A66CF4F25D109780006E4C1 /* CDPhishingItem+CoreDataDecodable.swift in Sources */, @@ -18865,6 +18868,7 @@ AEA0C8B4267BA40C00F9666F /* SelectedValidatorListViewModelFactory.swift in Sources */, 6ECD0116CD39D8F55D246864 /* SelectValidatorsConfirmPresenter.swift in Sources */, 84906FEA28AFCC3F0049B57D /* LoadableStackCellView.swift in Sources */, + 840EAE6B29FB8AEA00453C7E /* WalletConnectMethod.swift in Sources */, 84C3420728314D9600156569 /* ParaStkScheduledRequestsQueryFactory.swift in Sources */, CC545DF80038901FA06FDD58 /* SelectValidatorsConfirmViewController.swift in Sources */, 1F88F3DBFA0BD6D0FDF558F3 /* SelectValidatorsConfirmViewFactory.swift in Sources */, diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectDecision.swift b/novawallet/Common/Services/WalletConnect/WalletConnectDecision.swift new file mode 100644 index 0000000000..bc0c285e92 --- /dev/null +++ b/novawallet/Common/Services/WalletConnect/WalletConnectDecision.swift @@ -0,0 +1,26 @@ +import Foundation +import WalletConnectSwiftV2 + +enum WalletConnectProposalDecision { + case approve(proposal: Session.Proposal, namespaces: [String: SessionNamespace]) + case reject(proposal: Session.Proposal) +} + +struct WalletConnectSignDecision { + let request: Request + let result: RPCResult + + static func approve(request: Request, signature: AnyCodable) -> WalletConnectSignDecision { + .init(request: request, result: .response(signature)) + } + + static func reject(request: Request) -> WalletConnectSignDecision { + let error = WalletConnectSwiftV2.JSONRPCError( + code: 4001, + message: "Rejected", + data: nil + ) + + return .init(request: request, result: .error(error)) + } +} diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift b/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift deleted file mode 100644 index a19abe78e9..0000000000 --- a/novawallet/Common/Services/WalletConnect/WalletConnectProposalDecision.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation -import WalletConnectSwiftV2 - -enum WalletConnectProposalDecision { - case approve(proposal: Session.Proposal, namespaces: [String: SessionNamespace]) - case reject(proposal: Session.Proposal) -} diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index da06e366a7..b8039547b4 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -16,12 +16,14 @@ protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { func connect(uri: String) func submit(proposalDecision: WalletConnectProposalDecision) + func submit(signingDecision: WalletConnectSignDecision) } enum WalletConnectServiceError: Error { case setupNeeded case connectFailed(uri: String, internalError: Error) case proposalFailed(decision: WalletConnectProposalDecision, internalError: Error) + case signFailed(decision: WalletConnectSignDecision, internalError: Error) } final class WalletConnectService { @@ -193,6 +195,26 @@ extension WalletConnectService: WalletConnectServiceProtocol { } } + func submit(signingDecision: WalletConnectSignDecision) { + guard let client = client else { + notify(error: .setupNeeded) + return + } + + Task { [weak self] in + do { + try await client.respond( + topic: signingDecision.request.topic, + requestId: signingDecision.request.id, + response: signingDecision.result + ) + } catch { + self?.logger.error("Signature submission failed: \(error)") + self?.notify(error: .signFailed(decision: signingDecision, internalError: error)) + } + } + } + func setup() { setupNetworking() setupPairing() diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift new file mode 100644 index 0000000000..55c2e18429 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift @@ -0,0 +1,11 @@ +import Foundation + +enum WalletConnectMethod: String { + case polkadotSignTransaction = "polkadot_signTransaction" + case polkadotSignMessage = "polkadot_signMessage" + case ethSignTransaction = "eth_signTransaction" + case ethSendTransaction = "eth_sendTransaction" + case ethSign = "eth_sign" + case ethPersonalSign = "personal_sign" + case ethSignTypeData = "eth_signTypedData" +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift index e6e20b992e..45d545738d 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift @@ -171,4 +171,9 @@ extension WalletConnectModelFactory { return .init(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) } + + static func resolveChain(for blockchain: Blockchain, chainsStore: ChainsStoreProtocol) -> ChainModel? { + let resolution = resolveChains(from: [blockchain], chainsStore: chainsStore) + return resolution.resolved.first?.value + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index bf799152ad..9432f90707 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -65,6 +65,26 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { ) stateMachine.emit(authRequest: authRequest, nextState: nextState) + } + + private func processSign(request: Request, dataSource: DAppStateDataSource) { + guard let method = WalletConnectMethod(rawValue: request.method) else { + // TODO: Reject unsupported method + return + } + + guard + let chains = WalletConnectModelFactory.resolveChain( + for: request.chainId, + chainsStore: dataSource.chainsStore + ) else { + // TODO: Reject unsupported chain + return + } + + + + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift index 85c9568b8d..2544747863 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift @@ -10,6 +10,7 @@ protocol WalletConnectStateMachineProtocol: AnyObject { nextState: WalletConnectStateProtocol ) func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) + func emit(signDecision: WalletConnectSignDecision, nextState: WalletConnectStateProtocol) func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index bea6db9602..fb43c7de59 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -16,17 +16,15 @@ extension WalletConnectStateSigning: WalletConnectStateProtocol { false } - func handle(message: WalletConnectTransportMessage, dataSource: DAppStateDataSource) { + func handle(message: WalletConnectTransportMessage, dataSource _: DAppStateDataSource) { emitUnexpected(message: message, nextState: self) } - func handleOperation(response: DAppOperationResponse, dataSource: DAppStateDataSource) { + func handleOperation(response _: DAppOperationResponse, dataSource _: DAppStateDataSource) {} - } - - func handleAuth(response: DAppAuthResponse, dataSource: DAppStateDataSource) { + func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { emitUnexpected(message: response, nextState: self) } - func proceed(with dataSource: DAppStateDataSource) {} + func proceed(with _: DAppStateDataSource) {} } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 8fcb70763e..49a84d0977 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -138,6 +138,14 @@ extension WalletConnectTransport: WalletConnectStateMachineProtocol { nextState.proceed(with: dataSource) } + func emit(signDecision: WalletConnectSignDecision, nextState: WalletConnectStateProtocol) { + state = nextState + + service.submit(signingDecision: signDecision) + + nextState.proceed(with: dataSource) + } + func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) { state = nextState From d2899b99fdf45e33569b91eb0d2d3507c051db27 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 29 Apr 2023 12:20:31 +0500 Subject: [PATCH 12/54] signing presentation --- novawallet.xcodeproj/project.pbxproj | 4 + .../Extension/Foundation/String+Helpers.swift | 4 + .../Evm/EvmTransactionService.swift | 6 +- .../WalletConnect/WalletConnectService.swift | 10 +- .../Transports/DAppMetamaskTransport.swift | 2 +- .../DAppEthereumConfirmInteractor.swift | 5 +- .../DAppEthereumSignBytesInteractor.swift | 13 +- ...erationConfirmInteractor+Proccessing.swift | 12 +- .../DAppOperationConfirmViewFactory.swift | 21 +- .../DAppSignBytesConfirmInteractor.swift | 13 +- .../Modules/DApp/Model/DAppSigningType.swift | 5 +- .../MetamaskProtocol/MetamaskChain.swift | 29 ++ .../Model/WalletConnectMethod.swift | 1 - .../Model/WalletConnectSignModelFactory.swift | 277 ++++++++++++++++++ .../Model/WalletConnectTransportMessage.swift | 2 +- .../States/WalletConnectStateNewMessage.swift | 68 ++++- .../States/WalletConnectStateSigning.swift | 18 +- .../Transport/WalletConnectTransport.swift | 4 +- 18 files changed, 461 insertions(+), 33 deletions(-) create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index a196f1f465..0804934109 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1470,6 +1470,7 @@ 8483B15528F9406C0048B295 /* ReferendumVotersInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8483B15428F9406C0048B295 /* ReferendumVotersInteractorError.swift */; }; 8483B15828F98C9F0048B295 /* ReferendumVotersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8483B15728F98C9F0048B295 /* ReferendumVotersViewModel.swift */; }; 8483B15D28FA79620048B295 /* ReferendumDisplayStringFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8483B15C28FA79620048B295 /* ReferendumDisplayStringFactory.swift */; }; + 8485035029FBC84300AE6909 /* WalletConnectSignModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8485034F29FBC84300AE6909 /* WalletConnectSignModelFactory.swift */; }; 8485D924277E16C400767243 /* DAppBrowserScriptHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8485D923277E16C400767243 /* DAppBrowserScriptHandler.swift */; }; 8487010A2907055900F2C0C3 /* ReferendumTimelineViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848701092907055900F2C0C3 /* ReferendumTimelineViewModelFactory.swift */; }; 8487010C2907AA9B00F2C0C3 /* ReferendumMetadataViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487010B2907AA9B00F2C0C3 /* ReferendumMetadataViewModelFactory.swift */; }; @@ -4903,6 +4904,7 @@ 8483B15428F9406C0048B295 /* ReferendumVotersInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumVotersInteractorError.swift; sourceTree = ""; }; 8483B15728F98C9F0048B295 /* ReferendumVotersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumVotersViewModel.swift; sourceTree = ""; }; 8483B15C28FA79620048B295 /* ReferendumDisplayStringFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumDisplayStringFactory.swift; sourceTree = ""; }; + 8485034F29FBC84300AE6909 /* WalletConnectSignModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSignModelFactory.swift; sourceTree = ""; }; 8485D923277E16C400767243 /* DAppBrowserScriptHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppBrowserScriptHandler.swift; sourceTree = ""; }; 848701092907055900F2C0C3 /* ReferendumTimelineViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumTimelineViewModelFactory.swift; sourceTree = ""; }; 8487010B2907AA9B00F2C0C3 /* ReferendumMetadataViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataViewModelFactory.swift; sourceTree = ""; }; @@ -11890,6 +11892,7 @@ 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */, 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */, 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */, + 8485034F29FBC84300AE6909 /* WalletConnectSignModelFactory.swift */, ); path = Model; sourceTree = ""; @@ -18464,6 +18467,7 @@ 849A4EF6279A7AEF00AB6709 /* StateminAssetExtras.swift in Sources */, 849E17E627914394002D1744 /* NavigationBarSettings.swift in Sources */, 84B8AA7329F8EFC700347A37 /* DAppInteractionError.swift in Sources */, + 8485035029FBC84300AE6909 /* WalletConnectSignModelFactory.swift in Sources */, 849E17F02791909C002D1744 /* DAppSettings.swift in Sources */, 84DBEA7729DAF5EC00A504A7 /* ConnectionNodeSwitchCode.swift in Sources */, 884048D428C723F00085FFA6 /* OrmlTokenSubscriptionHandlingFactory.swift in Sources */, diff --git a/novawallet/Common/Extension/Foundation/String+Helpers.swift b/novawallet/Common/Extension/Foundation/String+Helpers.swift index 5c56470548..838d42a6be 100644 --- a/novawallet/Common/Extension/Foundation/String+Helpers.swift +++ b/novawallet/Common/Extension/Foundation/String+Helpers.swift @@ -15,4 +15,8 @@ extension String { return self } } + + func isHex() -> Bool { + hasPrefix("0x") && lengthOfBytes(using: .ascii) % 2 == 0 + } } diff --git a/novawallet/Common/Services/ExtrinsicService/Evm/EvmTransactionService.swift b/novawallet/Common/Services/ExtrinsicService/Evm/EvmTransactionService.swift index c836e71630..6376b039e7 100644 --- a/novawallet/Common/Services/ExtrinsicService/Evm/EvmTransactionService.swift +++ b/novawallet/Common/Services/ExtrinsicService/Evm/EvmTransactionService.swift @@ -87,7 +87,11 @@ extension EvmTransactionService: EvmTransactionServiceProtocol { let builder = EvmTransactionBuilder(address: address, chainId: chain.evmChainId) let transaction = (try closure(builder)).buildTransaction() - let gasEstimationWrapper = createGasLimitOrDefaultWrapper(for: transaction, fallbackGasLimit: fallbackGasLimit) + let gasEstimationWrapper = createGasLimitOrDefaultWrapper( + for: transaction, + fallbackGasLimit: fallbackGasLimit + ) + let gasPriceOperation = operationFactory.createGasPriceOperation() let mapOperation = ClosureOperation { diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index b8039547b4..60bd669522 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -6,7 +6,7 @@ import Combine protocol WalletConnectServiceDelegate: AnyObject { func walletConnect(service: WalletConnectServiceProtocol, proposal: Session.Proposal) func walletConnect(service: WalletConnectServiceProtocol, establishedSession: Session) - func walletConnect(service: WalletConnectServiceProtocol, request: Request) + func walletConnect(service: WalletConnectServiceProtocol, request: Request, session: Session?) func walletConnect(service: WalletConnectServiceProtocol, error: WalletConnectServiceError) } @@ -79,7 +79,13 @@ final class WalletConnectService { return } - strongSelf.delegate?.walletConnect(service: strongSelf, request: request) + let session = strongSelf.client?.getSessions().first { $0.topic == request.topic } + + strongSelf.delegate?.walletConnect( + service: strongSelf, + request: request, + session: session + ) } } diff --git a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift index e3e00bb067..a2f0683447 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift @@ -82,7 +82,7 @@ extension DAppMetamaskTransport: DAppMetamaskStateMachineProtocol { if let request = createConfirmationRequest(messageId: messageId, from: signingOperation) { if signingOperation.stringValue == nil { - let type = DAppSigningType.ethereumTransaction(chain: nextState.chain) + let type = DAppSigningType.ethereumSendTransaction(chain: nextState.chain) delegate?.dAppTransport(self, didReceiveConfirmation: request, of: type) } else if let accountId = try? state?.fetchSelectedAddress(from: dataSource)?.toAccountId() { let type = DAppSigningType.ethereumBytes(chain: nextState.chain, accountId: accountId) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift index 34081a51f2..9255be9990 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift @@ -11,6 +11,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { let operationQueue: OperationQueue let signingWrapperFactory: SigningWrapperFactoryProtocol let serializationFactory: EthereumSerializationFactoryProtocol + let shouldSendTransaction: Bool init( request: DAppOperationRequest, @@ -18,7 +19,8 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { ethereumOperationFactory: EthereumOperationFactoryProtocol, operationQueue: OperationQueue, signingWrapperFactory: SigningWrapperFactoryProtocol, - serializationFactory: EthereumSerializationFactoryProtocol + serializationFactory: EthereumSerializationFactoryProtocol, + shouldSendTransaction: Bool ) { self.request = request self.chain = chain @@ -26,6 +28,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { self.operationQueue = operationQueue self.signingWrapperFactory = signingWrapperFactory self.serializationFactory = serializationFactory + self.shouldSendTransaction = shouldSendTransaction } private func createSigningTransactionWrapper( diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift index ff947ecb20..173ea56cf0 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift @@ -65,8 +65,17 @@ final class DAppEthereumSignBytesInteractor: DAppOperationBaseInteractor { } private func prepareRawBytes() throws -> Data { - if case let .stringValue(hexValue) = request.operationData { - return try Data(hexString: hexValue) + if case let .stringValue(stringValue) = request.operationData { + if stringValue.isHex() { + return try Data(hexString: stringValue) + } else { + guard let data = stringValue.data(using: .utf8) else { + throw CommonError.dataCorruption + } + + return data + } + } else { throw CommonError.dataCorruption } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift index 456b07b284..43b96c0603 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift @@ -79,15 +79,19 @@ extension DAppOperationConfirmInteractor { ) } + // TODO: Find out whether to return this validation + guard - let specVersion = BigUInt.fromHexString(extrinsic.specVersion), - codingFactory.specVersion == specVersion else { + let specVersion = BigUInt.fromHexString(extrinsic.specVersion) /* , + codingFactory.specVersion == specVersion */ else { throw DAppOperationConfirmInteractorError.extrinsicBadField(name: "specVersion") } + // TODO: Find out whether to return this validation + guard - let transactionVersion = BigUInt.fromHexString(extrinsic.transactionVersion), - codingFactory.txVersion == transactionVersion else { + let transactionVersion = BigUInt.fromHexString(extrinsic.transactionVersion) /*, + codingFactory.txVersion == transactionVersion*/ else { throw DAppOperationConfirmInteractorError.extrinsicBadField(name: "transactionVersion") } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift index 408372de8b..a96799036c 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift @@ -18,9 +18,20 @@ struct DAppOperationConfirmViewFactory { case let .bytes(chain): maybeAssetInfo = chain.utilityAssets().first?.displayInfo(with: chain.icon) maybeInteractor = createSignBytesInteractor(for: request, chain: chain) - case let .ethereumTransaction(chain): + case let .ethereumSendTransaction(chain): maybeAssetInfo = chain.assetDisplayInfo - maybeInteractor = createEthereumInteractor(for: request, chain: chain) + maybeInteractor = createEthereumInteractor( + for: request, + chain: chain, + shouldSendTransaction: true + ) + case let .ethereumSignTransaction(chain): + maybeAssetInfo = chain.assetDisplayInfo + maybeInteractor = createEthereumInteractor( + for: request, + chain: chain, + shouldSendTransaction: false + ) case let .ethereumBytes(chain, accountId): maybeAssetInfo = chain.assetDisplayInfo maybeInteractor = createEthereumPersonalSignInteractor( @@ -102,7 +113,8 @@ struct DAppOperationConfirmViewFactory { private static func createEthereumInteractor( for request: DAppOperationRequest, - chain: MetamaskChain + chain: MetamaskChain, + shouldSendTransaction: Bool ) -> DAppEthereumConfirmInteractor? { guard let rpcUrlString = chain.rpcUrls.first, let rpcUrl = URL(string: rpcUrlString) else { return nil @@ -116,7 +128,8 @@ struct DAppOperationConfirmViewFactory { ethereumOperationFactory: operationFactory, operationQueue: OperationManagerFacade.sharedDefaultQueue, signingWrapperFactory: SigningWrapperFactory(keystore: Keychain()), - serializationFactory: EthereumSerializationFactory() + serializationFactory: EthereumSerializationFactory(), + shouldSendTransaction: shouldSendTransaction ) } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift index 9c40075ae5..52defddef3 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift @@ -63,8 +63,17 @@ final class DAppSignBytesConfirmInteractor: DAppOperationBaseInteractor { } private func prepareRawBytes() throws -> Data { - if case let .stringValue(hexValue) = request.operationData { - return try Data(hexString: hexValue) + if case let .stringValue(stringValue) = request.operationData { + if stringValue.isHex() { + return try Data(hexString: stringValue) + } else { + guard let data = stringValue.data(using: .utf8) else { + throw CommonError.dataCorruption + } + + return data + } + } else { return try JSONEncoder().encode(request.operationData) } diff --git a/novawallet/Modules/DApp/Model/DAppSigningType.swift b/novawallet/Modules/DApp/Model/DAppSigningType.swift index b4e0f60a57..152e145e62 100644 --- a/novawallet/Modules/DApp/Model/DAppSigningType.swift +++ b/novawallet/Modules/DApp/Model/DAppSigningType.swift @@ -3,7 +3,8 @@ import Foundation enum DAppSigningType { case extrinsic(chain: ChainModel) case bytes(chain: ChainModel) - case ethereumTransaction(chain: MetamaskChain) + case ethereumSendTransaction(chain: MetamaskChain) + case ethereumSignTransaction(chain: MetamaskChain) case ethereumBytes(chain: MetamaskChain, accountId: AccountId) var msgType: PolkadotExtensionMessage.MessageType? { @@ -12,7 +13,7 @@ enum DAppSigningType { return .signExtrinsic case .bytes: return .signBytes - case .ethereumTransaction, .ethereumBytes: + case .ethereumSignTransaction, .ethereumSendTransaction, .ethereumBytes: return nil } } diff --git a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift b/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift index 999bb92586..4049898038 100644 --- a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift +++ b/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift @@ -1,4 +1,5 @@ import Foundation +import BigInt struct MetamaskChain: Codable { struct NativeCurrency: Codable { @@ -53,3 +54,31 @@ extension MetamaskChain { ) } } + +extension MetamaskChain { + init?(chain: ChainModel) { + guard let asset = chain.utilityAsset() else { + return nil + } + + chainId = BigUInt(chain.addressPrefix).toHexWithPrefix() + chainName = chain.name + nativeCurrency = .init( + name: asset.name ?? chain.name, + symbol: asset.symbol, + decimals: Int16(bitPattern: asset.precision) + ) + + // TODO: Fix node retrieval + + if let node = chain.nodes.first(where: { $0.url.hasPrefix(ConnectionNodeSchema.https) }) { + rpcUrls = [node.url] + } else { + rpcUrls = [] + } + + blockExplorerUrls = nil + + iconUrls = [chain.icon.absoluteString] + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift index 55c2e18429..6512bec827 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectMethod.swift @@ -5,7 +5,6 @@ enum WalletConnectMethod: String { case polkadotSignMessage = "polkadot_signMessage" case ethSignTransaction = "eth_signTransaction" case ethSendTransaction = "eth_sendTransaction" - case ethSign = "eth_sign" case ethPersonalSign = "personal_sign" case ethSignTypeData = "eth_signTypedData" } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift new file mode 100644 index 0000000000..c04b336deb --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -0,0 +1,277 @@ +import Foundation +import WalletConnectSwiftV2 +import SubstrateSdk + +enum WalletConnectSignModelFactoryError: Error { + case missingAccount(chainId: ChainModel.Id) + case invalidParams(params: JSON, method: WalletConnectMethod) + case invalidChain(expected: ChainModel.Id, actual: ChainModel.Id) + case invalidAccount(expected: AccountAddress, actual: AccountAddress) +} + +enum WalletConnectSignModelFactory { + private static func parseAndValidatePolkadotParams( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable + ) throws -> JSON { + guard let walletAddress = wallet.fetch(for: chain.accountRequest())?.toAddress() else { + throw WalletConnectSignModelFactoryError.missingAccount(chainId: chain.chainId) + } + + let json = try params.get(JSON.self) + + guard let requestAddress = json.address?.stringValue else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + // Wallet Connect can include addresses without checksum + + guard walletAddress.lowercased() == requestAddress.lowercased() else { + throw WalletConnectSignModelFactoryError.invalidAccount( + expected: walletAddress, + actual: requestAddress + ) + } + + return json + } + + private static func createPolkadotSignTransaction( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable + ) throws -> JSON { + let json = try parseAndValidatePolkadotParams( + for: wallet, + chain: chain, + params: params + ) + + guard + let payload = try json.transactionPayload?.map(to: PolkadotExtensionExtrinsic.self), + let address = wallet.fetch(for: chain.accountRequest())?.toAddress() else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + // Wallet Connect can include address without checksum, we manually add it for consistency + + let modifiedPayload = PolkadotExtensionExtrinsic( + address: address, + blockHash: payload.blockHash, + blockNumber: payload.blockNumber, + era: payload.era, + genesisHash: payload.genesisHash, + method: payload.method, + nonce: payload.nonce, + specVersion: payload.specVersion, + tip: payload.tip, + transactionVersion: payload.transactionVersion, + signedExtensions: payload.signedExtensions, + version: payload.version + ) + + return try modifiedPayload.toScaleCompatibleJSON() + } + + private static func createPolkadotSignMessage( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable + ) throws -> JSON { + let json = try parseAndValidatePolkadotParams( + for: wallet, + chain: chain, + params: params + ) + + guard let messageJson = json.message, messageJson.stringValue != nil else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + return messageJson + } + + private static func createEthereumTransaction(for params: AnyCodable) throws -> JSON { + let json = try params.get(JSON.self) + + guard let transaction = json.arrayValue?.first else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + return transaction + } + + private static func parseAndValidateEthereumParams( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable, + accountIndex: Int + ) throws -> JSON { + guard let accountId = wallet.fetch(for: chain.accountRequest())?.accountId else { + throw WalletConnectSignModelFactoryError.missingAccount(chainId: chain.chainId) + } + + let json = try params.get(JSON.self) + + guard + let arrayParams = json.arrayValue, + let txAccountId = try? arrayParams[safe: accountIndex]?.stringValue?.toAccountId( + using: chain.chainFormat + ) else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + guard accountId == txAccountId else { + throw WalletConnectSignModelFactoryError.invalidAccount( + expected: try accountId.toAddress(using: chain.chainFormat), + actual: try txAccountId.toAddress(using: chain.chainFormat) + ) + } + + return json + } + + private static func createPersonalSignMessage( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable + ) throws -> JSON { + let json = try parseAndValidateEthereumParams( + for: wallet, + chain: chain, + params: params, + accountIndex: 1 + ) + + guard + let hexString = json.arrayValue?.first?.stringValue, + let signingHashedData = try? Data( + hexString: hexString + ).ethereumPersonalSignMessage()?.keccak256() else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + return JSON.stringValue(signingHashedData.toHex(includePrefix: true)) + } + + private static func createSignTypedDataMessage( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable + ) throws -> JSON { + let json = try parseAndValidateEthereumParams( + for: wallet, + chain: chain, + params: params, + accountIndex: 0 + ) + + guard let typedDataString = json.arrayValue?.last?.stringValue else { + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } + + let isHex = typedDataString.isHex() + + guard !isHex else { + return JSON.stringValue(typedDataString) + } + + throw WalletConnectSignModelFactoryError.invalidParams( + params: json, + method: .polkadotSignTransaction + ) + } +} + +extension WalletConnectSignModelFactory { + static func createOperationData( + for wallet: MetaAccountModel, + chain: ChainModel, + params: AnyCodable, + method: WalletConnectMethod + ) throws -> JSON { + switch method { + case .polkadotSignTransaction: + return try createPolkadotSignTransaction( + for: wallet, + chain: chain, + params: params + ) + case .polkadotSignMessage: + return try createPolkadotSignMessage( + for: wallet, + chain: chain, + params: params + ) + case .ethSignTransaction, .ethSendTransaction: + return try createEthereumTransaction(for: params) + case .ethPersonalSign: + return try createPersonalSignMessage( + for: wallet, + chain: chain, + params: params + ) + case .ethSignTypeData: + return try createSignTypedDataMessage( + for: wallet, + chain: chain, + params: params + ) + } + } + + static func createSigningType( + for wallet: MetaAccountModel, + chain: ChainModel, + method: WalletConnectMethod + ) throws -> DAppSigningType { + switch method { + case .polkadotSignTransaction: + return .extrinsic(chain: chain) + case .polkadotSignMessage: + return .bytes(chain: chain) + case .ethSendTransaction: + guard let metamaskChain = MetamaskChain(chain: chain) else { + throw CommonError.dataCorruption + } + + return .ethereumSendTransaction(chain: metamaskChain) + case .ethSignTransaction: + guard let metamaskChain = MetamaskChain(chain: chain) else { + throw CommonError.dataCorruption + } + + return .ethereumSignTransaction(chain: metamaskChain) + case .ethPersonalSign, .ethSignTypeData: + guard + let metamaskChain = MetamaskChain(chain: chain), + let accountId = wallet.fetch(for: chain.accountRequest())?.accountId else { + throw CommonError.dataCorruption + } + + return .ethereumBytes(chain: metamaskChain, accountId: accountId) + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift index 98ce28f142..abe378f7bc 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectTransportMessage.swift @@ -3,7 +3,7 @@ import WalletConnectSwiftV2 enum WalletConnectTransportMessage { case proposal(Session.Proposal) - case request(Request) + case request(Request, Session?) var host: String? { switch self { diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index 9432f90707..ac5564e72a 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -1,5 +1,6 @@ import Foundation import WalletConnectSwiftV2 +import SubstrateSdk final class WalletConnectStateNewMessage: WalletConnectBaseState { struct ResolutionResult { @@ -67,24 +68,73 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { stateMachine.emit(authRequest: authRequest, nextState: nextState) } - private func processSign(request: Request, dataSource: DAppStateDataSource) { + private func rejectRequest(request: Request) { + guard let stateMachine = stateMachine else { + return + } + + let desicion = WalletConnectSignDecision.reject(request: request) + + stateMachine.emit( + signDecision: desicion, + nextState: WalletConnectStateReady(stateMachine: stateMachine) + ) + } + + private func processSign(request: Request, session: Session?, dataSource: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + guard let method = WalletConnectMethod(rawValue: request.method) else { - // TODO: Reject unsupported method + rejectRequest(request: request) return } guard - let chains = WalletConnectModelFactory.resolveChain( + let chain = WalletConnectModelFactory.resolveChain( for: request.chainId, chainsStore: dataSource.chainsStore ) else { - // TODO: Reject unsupported chain + rejectRequest(request: request) return } - - - + do { + let operationData = try WalletConnectSignModelFactory.createOperationData( + for: dataSource.walletSettings.value, + chain: chain, + params: request.params, + method: method + ) + + let signingType = try WalletConnectSignModelFactory.createSigningType( + for: dataSource.walletSettings.value, + chain: chain, + method: method + ) + + let signingRequest = DAppOperationRequest( + transportName: DAppTransports.walletConnect, + identifier: request.id.string, + wallet: dataSource.walletSettings.value, + dApp: session?.peer.name ?? "", + dAppIcon: session?.peer.icons.first.flatMap { URL(string: $0) }, + operationData: operationData + ) + + let nextState = WalletConnectStateSigning(request: request, stateMachine: stateMachine) + + stateMachine.emit( + signingRequest: signingRequest, + type: signingType, + nextState: nextState + ) + } catch { + // TODO: Handle error + + rejectRequest(request: request) + } } } @@ -107,8 +157,8 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { switch message { case let .proposal(proposal): process(proposal: proposal, dataSource: dataSource) - case .request: - break + case let .request(request, session): + processSign(request: request, session: session, dataSource: dataSource) } } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index fb43c7de59..276d53b2d6 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -20,7 +20,23 @@ extension WalletConnectStateSigning: WalletConnectStateProtocol { emitUnexpected(message: message, nextState: self) } - func handleOperation(response _: DAppOperationResponse, dataSource _: DAppStateDataSource) {} + func handleOperation(response: DAppOperationResponse, dataSource _: DAppStateDataSource) { + guard let stateMachine = stateMachine else { + return + } + + let nextState = WalletConnectStateReady(stateMachine: stateMachine) + + if let signature = response.signature { + let result = AnyCodable(any: signature.toHex(includePrefix: true)) + stateMachine.emit( + signDecision: .approve(request: request, signature: result), + nextState: nextState + ) + } else { + stateMachine.emit(signDecision: .reject(request: request), nextState: nextState) + } + } func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { emitUnexpected(message: response, nextState: self) diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 49a84d0977..8c130ec466 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -168,10 +168,10 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { // TODO: Handle session } - func walletConnect(service _: WalletConnectServiceProtocol, request: Request) { + func walletConnect(service _: WalletConnectServiceProtocol, request: Request, session: Session?) { logger.debug("New session: \(request)") - delegate?.walletConnect(transport: self, didReceive: .request(request)) + delegate?.walletConnect(transport: self, didReceive: .request(request, session)) } func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { From b0958df32aa3f4797942698544a1fe4c9fa0b109 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sun, 30 Apr 2023 08:55:03 +0500 Subject: [PATCH 13/54] support ethereum sign --- novawallet.xcodeproj/project.pbxproj | 4 - .../Ethereum/EthereumTransaction.swift | 2 +- .../DAppEthereumConfirmInteractor.swift | 161 +++++++++++++----- ...erationConfirmInteractor+Proccessing.swift | 4 +- .../MetamaskTransaction.swift | 36 ---- .../States/WalletConnectStateSigning.swift | 4 +- 6 files changed, 124 insertions(+), 87 deletions(-) delete mode 100644 novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskTransaction.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 0804934109..dd55c05241 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1842,7 +1842,6 @@ 84A2C90C24E192F50020D3B7 /* ShakeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A2C90B24E192F50020D3B7 /* ShakeAnimator.swift */; }; 84A2CD5325685A4100A9FB0D /* WalletHistoryViewFactoryOverriding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A2CD5225685A4100A9FB0D /* WalletHistoryViewFactoryOverriding.swift */; }; 84A3034926A834F900E64382 /* ValidatorInfoViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A3034826A834F900E64382 /* ValidatorInfoViewLayout.swift */; }; - 84A3772E27B502F20026D6D1 /* MetamaskTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A3772D27B502F20026D6D1 /* MetamaskTransaction.swift */; }; 84A3773027B52E020026D6D1 /* DAppEthereumConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A3772F27B52E020026D6D1 /* DAppEthereumConfirmInteractor.swift */; }; 84A3B8A02836D74B00DE2669 /* StorageKeyDecodingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A3B89F2836D74B00DE2669 /* StorageKeyDecodingProtocol.swift */; }; 84A3B8A22836DA2600DE2669 /* LastAccountIdKeyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A3B8A12836DA2600DE2669 /* LastAccountIdKeyWrapper.swift */; }; @@ -5284,7 +5283,6 @@ 84A2C90B24E192F50020D3B7 /* ShakeAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShakeAnimator.swift; sourceTree = ""; }; 84A2CD5225685A4100A9FB0D /* WalletHistoryViewFactoryOverriding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletHistoryViewFactoryOverriding.swift; sourceTree = ""; }; 84A3034826A834F900E64382 /* ValidatorInfoViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatorInfoViewLayout.swift; sourceTree = ""; }; - 84A3772D27B502F20026D6D1 /* MetamaskTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetamaskTransaction.swift; sourceTree = ""; }; 84A3772F27B52E020026D6D1 /* DAppEthereumConfirmInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppEthereumConfirmInteractor.swift; sourceTree = ""; }; 84A3B89F2836D74B00DE2669 /* StorageKeyDecodingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageKeyDecodingProtocol.swift; sourceTree = ""; }; 84A3B8A12836DA2600DE2669 /* LastAccountIdKeyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastAccountIdKeyWrapper.swift; sourceTree = ""; }; @@ -11730,7 +11728,6 @@ 849976C327B286AF00B14A6C /* MetamaskMessage.swift */, 849976CF27B3AC0100B14A6C /* MetamaskEvent.swift */, 849976D127B3AC3000B14A6C /* MetamaskChain.swift */, - 84A3772D27B502F20026D6D1 /* MetamaskTransaction.swift */, 841BFD6A27BA5765000A16CE /* MetamaskError.swift */, 841BFD6C27BA8E70000A16CE /* MetamaskSwitchChain.swift */, ); @@ -17751,7 +17748,6 @@ 8489A6D827FDA51C0040C066 /* AccountLocalSubscriptionHandler.swift in Sources */, 8406B5AB26FBD9EB00635B61 /* AccountInfoUpdatingService.swift in Sources */, 844B2E7A27C425E3000CC079 /* NftLocalStorageSubscriber.swift in Sources */, - 84A3772E27B502F20026D6D1 /* MetamaskTransaction.swift in Sources */, AE6F7FE62685F2C3002BBC3E /* ValidatorListFilterViewModel.swift in Sources */, 886CA9622977E9B300FC255A /* GovernanceDelegateTableViewCell.swift in Sources */, F40966F626B299FC008CD244 /* SubqueryStakeSource.swift in Sources */, diff --git a/novawallet/Common/Network/Ethereum/EthereumTransaction.swift b/novawallet/Common/Network/Ethereum/EthereumTransaction.swift index fc6598995f..a9150f11e4 100644 --- a/novawallet/Common/Network/Ethereum/EthereumTransaction.swift +++ b/novawallet/Common/Network/Ethereum/EthereumTransaction.swift @@ -16,7 +16,7 @@ struct EthereumTransaction: Codable { extension EthereumTransaction { static func gasEstimationTransaction( - from transaction: MetamaskTransaction + from transaction: EthereumTransaction ) -> EthereumTransaction { EthereumTransaction( from: transaction.from, diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift index 9255be9990..1d7cf7cbe8 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift @@ -31,34 +31,57 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { self.shouldSendTransaction = shouldSendTransaction } + private func createGasLimitOperation(for transaction: EthereumTransaction) -> BaseOperation { + if let gasLimit = transaction.gas, let value = try? BigUInt(hex: gasLimit), value > 0 { + return BaseOperation.createWithResult(gasLimit) + } else { + let gasTransaction = EthereumTransaction.gasEstimationTransaction(from: transaction) + return ethereumOperationFactory.createGasLimitOperation(for: gasTransaction) + } + } + + private func createGasPriceOperation(for transaction: EthereumTransaction) -> BaseOperation { + if let gasPrice = transaction.gasPrice, let value = try? BigUInt(hex: gasPrice), value > 0 { + return BaseOperation.createWithResult(gasPrice) + } else { + return ethereumOperationFactory.createGasPriceOperation() + } + } + + private func createNonceOperation(for transaction: EthereumTransaction) -> BaseOperation { + if let nonce = transaction.nonce { + return BaseOperation.createWithResult(nonce) + } else { + guard let addressData = try? Data(hexString: transaction.from) else { + let error = DAppOperationConfirmInteractorError.extrinsicBadField(name: "from") + return BaseOperation.createWithError(error) + } + + return ethereumOperationFactory.createTransactionsCountOperation( + for: addressData, + block: .pending + ) + } + } + private func createSigningTransactionWrapper( for request: DAppOperationRequest ) -> CompoundOperationWrapper { - guard let transaction = try? request.operationData.map(to: MetamaskTransaction.self) else { + guard let transaction = try? request.operationData.map(to: EthereumTransaction.self) else { let error = DAppOperationConfirmInteractorError.extrinsicBadField(name: "root") return CompoundOperationWrapper.createWithError(error) } - guard let addressData = try? Data(hexString: transaction.from) else { - let error = DAppOperationConfirmInteractorError.extrinsicBadField(name: "from") - return CompoundOperationWrapper.createWithError(error) - } - - let nonceOperation = ethereumOperationFactory.createTransactionsCountOperation( - for: addressData, - block: .pending - ) - - let gasTransaction = EthereumTransaction.gasEstimationTransaction(from: transaction) - - let gasOperation = ethereumOperationFactory.createGasLimitOperation(for: gasTransaction) - let gasPriceOperation = ethereumOperationFactory.createGasPriceOperation() + let nonceOperation = createNonceOperation(for: transaction) + let gasOperation = createGasLimitOperation(for: transaction) + let gasPriceOperation = createGasPriceOperation(for: transaction) let mapOperation = ClosureOperation { let nonce = try nonceOperation.extractNoCancellableResultData() let gas = try gasOperation.extractNoCancellableResultData() let gasPrice = try gasPriceOperation.extractNoCancellableResultData() + let gasTransaction = EthereumTransaction.gasEstimationTransaction(from: transaction) return gasTransaction .replacing(gas: gas) .replacing(gasPrice: gasPrice) @@ -74,12 +97,20 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { private func createSerializationOperation( chain: MetamaskChain, dependingOn transactionOperation: BaseOperation, - signatureOperation: BaseOperation?, + signatureOperation: BaseOperation?, serializationFactory: EthereumSerializationFactoryProtocol ) -> BaseOperation { ClosureOperation { let transaction = try transactionOperation.extractNoCancellableResultData() - let maybeSignature = try signatureOperation?.extractNoCancellableResultData() + let maybeRawSignature = try signatureOperation?.extractNoCancellableResultData() + + let maybeSignature = try maybeRawSignature.map { rawSignature in + guard let signature = EthereumSignature(rawValue: rawSignature) else { + throw DAppOperationConfirmInteractorError.signingFailed + } + + return signature + } return try serializationFactory.serialize( transaction: transaction, @@ -94,7 +125,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { dependingOn signingDataOperation: BaseOperation, transactionOperation: BaseOperation, signingWrapperFactory: SigningWrapperFactoryProtocol - ) -> BaseOperation { + ) -> BaseOperation { ClosureOperation { let transaction = try transactionOperation.extractNoCancellableResultData() let signingData = try signingDataOperation.extractNoCancellableResultData() @@ -107,19 +138,13 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { let signingWrapper = signingWrapperFactory.createSigningWrapper(for: accountResponse) - let rawSignature = try signingWrapper.sign(signingData).rawData() - - guard let signature = EthereumSignature(rawValue: rawSignature) else { - throw DAppOperationConfirmInteractorError.signingFailed - } - - return signature + return try signingWrapper.sign(signingData).rawData() } } private func provideConfirmationModel() { guard - let transaction = try? request.operationData.map(to: MetamaskTransaction.self), + let transaction = try? request.operationData.map(to: EthereumTransaction.self), let chainAccountId = try? Data(hexString: transaction.from) else { presenter?.didReceive(feeResult: .failure(ChainAccountFetchingError.accountNotExists)) return @@ -149,7 +174,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { } private func provideFeeViewModel() { - guard let transaction = try? request.operationData.map(to: MetamaskTransaction.self) else { + guard let transaction = try? request.operationData.map(to: EthereumTransaction.self) else { let result: Result = .failure( DAppOperationConfirmInteractorError.extrinsicBadField(name: "root") ) @@ -157,10 +182,8 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { return } - let gasTransaction = EthereumTransaction.gasEstimationTransaction(from: transaction) - - let gasOperation = ethereumOperationFactory.createGasLimitOperation(for: gasTransaction) - let gasPriceOperation = ethereumOperationFactory.createGasPriceOperation() + let gasOperation = createGasLimitOperation(for: transaction) + let gasPriceOperation = createGasPriceOperation(for: transaction) let mapOperation = ClosureOperation { let gasHex = try gasOperation.extractNoCancellableResultData() @@ -199,19 +222,8 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { waitUntilFinished: false ) } -} - -extension DAppEthereumConfirmInteractor: DAppOperationConfirmInteractorInputProtocol { - func setup() { - provideConfirmationModel() - provideFeeViewModel() - } - func estimateFee() { - provideFeeViewModel() - } - - func confirm() { + private func confirmSend() { let transactionWrapper = createSigningTransactionWrapper(for: request) let signatureDataOperation = createSerializationOperation( chain: chain, @@ -273,6 +285,69 @@ extension DAppEthereumConfirmInteractor: DAppOperationConfirmInteractorInputProt operationQueue.addOperations(allOperations, waitUntilFinished: false) } + private func confirmSign() { + let transactionWrapper = createSigningTransactionWrapper(for: request) + let signatureDataOperation = createSerializationOperation( + chain: chain, + dependingOn: transactionWrapper.targetOperation, + signatureOperation: nil, + serializationFactory: serializationFactory + ) + + signatureDataOperation.addDependency(transactionWrapper.targetOperation) + + let signingOperation = createSigningOperation( + using: request.wallet, + dependingOn: signatureDataOperation, + transactionOperation: transactionWrapper.targetOperation, + signingWrapperFactory: signingWrapperFactory + ) + + signingOperation.addDependency(signatureDataOperation) + signingOperation.addDependency(transactionWrapper.targetOperation) + + signingOperation.completionBlock = { [weak self] in + DispatchQueue.main.async { + guard let strongSelf = self else { + return + } + + do { + let signature = try signingOperation.extractNoCancellableResultData() + let response = DAppOperationResponse(signature: signature) + let result: Result = .success(response) + strongSelf.presenter?.didReceive(responseResult: result, for: strongSelf.request) + } catch { + let result: Result = .failure(error) + strongSelf.presenter?.didReceive(responseResult: result, for: strongSelf.request) + } + } + } + + let allOperations = transactionWrapper.allOperations + [signatureDataOperation, signingOperation] + + operationQueue.addOperations(allOperations, waitUntilFinished: false) + } +} + +extension DAppEthereumConfirmInteractor: DAppOperationConfirmInteractorInputProtocol { + func setup() { + provideConfirmationModel() + provideFeeViewModel() + } + + func estimateFee() { + provideFeeViewModel() + } + + func confirm() { + if shouldSendTransaction { + confirmSend() + } else { + confirmSign() + } + } + func reject() { let response = DAppOperationResponse(signature: nil) presenter?.didReceive(responseResult: .success(response), for: request) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift index 43b96c0603..d1bc5bfe1a 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Proccessing.swift @@ -90,8 +90,8 @@ extension DAppOperationConfirmInteractor { // TODO: Find out whether to return this validation guard - let transactionVersion = BigUInt.fromHexString(extrinsic.transactionVersion) /*, - codingFactory.txVersion == transactionVersion*/ else { + let transactionVersion = BigUInt.fromHexString(extrinsic.transactionVersion) /* , + codingFactory.txVersion == transactionVersion */ else { throw DAppOperationConfirmInteractorError.extrinsicBadField(name: "transactionVersion") } diff --git a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskTransaction.swift b/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskTransaction.swift deleted file mode 100644 index dfc6ee2285..0000000000 --- a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskTransaction.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - -struct MetamaskTransaction: Codable { - /** - * 20 Bytes hex - The address the transaction is send from. - */ - let from: String - - /** - * 20 Bytes - (optional) The address the transaction is directed to. - */ - // swiftlint:disable:next identifier_name - let to: String? - - /** - * (optional, default: 90000) gas provided for the transaction execution. - * It will return unused gas. - */ - let gas: String? - - /** - * (optional, default: To-Be-Determined) Integer (in hex) of the gasPrice used for each paid gas - */ - let gasPrice: String? - - /** - * (optional) Integer (in hex) of the value sent with this transaction - */ - let value: String? - - /** - * Optional the compiled code of a contract OR the hash of the invoked method signature and encoded - * parameters. For details see Ethereum Contract ABI - */ - let data: String? -} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index 276d53b2d6..dc2530b91c 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -1,5 +1,6 @@ import Foundation import WalletConnectSwiftV2 +import SubstrateSdk final class WalletConnectStateSigning: WalletConnectBaseState { let request: Request @@ -28,7 +29,8 @@ extension WalletConnectStateSigning: WalletConnectStateProtocol { let nextState = WalletConnectStateReady(stateMachine: stateMachine) if let signature = response.signature { - let result = AnyCodable(any: signature.toHex(includePrefix: true)) + let result = AnyCodable(signature.toHex(includePrefix: true)) + stateMachine.emit( signDecision: .approve(request: request, signature: result), nextState: nextState From 7774a512c82b55fdcb8ab1c50a681b59a1e6eb09 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sun, 30 Apr 2023 09:39:24 +0500 Subject: [PATCH 14/54] fix matching chain --- novawallet/Common/Model/CAIP/CAIP-19/Caip19.swift | 4 ++-- novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift | 10 +++++++--- .../Common/Services/Web3Name/Web3NameService.swift | 2 +- .../Model/WalletConnectModelFactory.swift | 10 +++++++++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/novawallet/Common/Model/CAIP/CAIP-19/Caip19.swift b/novawallet/Common/Model/CAIP/CAIP-19/Caip19.swift index b0c3f587a7..006eb4bd6f 100644 --- a/novawallet/Common/Model/CAIP/CAIP-19/Caip19.swift +++ b/novawallet/Common/Model/CAIP/CAIP-19/Caip19.swift @@ -104,8 +104,8 @@ extension Caip19.AssetId { return coin } - func match(chainId: String, token: Caip19.RegisteredToken) -> Bool { - guard self.chainId.match(chainId) else { + func match(chain: ChainModel, token: Caip19.RegisteredToken) -> Bool { + guard chainId.match(chain) else { return false } diff --git a/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift b/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift index fd16cd8de2..9f1ba3f54c 100644 --- a/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift +++ b/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift @@ -63,12 +63,16 @@ extension Caip2.ChainId { } extension Caip2.ChainId { - func match(_ identifier: String) -> Bool { + func match(_ chain: ChainModel) -> Bool { switch knownChain { case let .polkadot(chainId): - return identifier.hasPrefix(chainId) + return chain.chainId.hasPrefix(chainId) case let .eip155(id): - return identifier.caseInsensitiveCompare("eip155:\(id)") == .orderedSame + if chain.isEthereumBased, BigUInt(chain.addressPrefix) == id { + return true + } + + return chain.chainId.caseInsensitiveCompare("eip155:\(id)") == .orderedSame default: return false } diff --git a/novawallet/Common/Services/Web3Name/Web3NameService.swift b/novawallet/Common/Services/Web3Name/Web3NameService.swift index f326b9a540..4d49de397f 100644 --- a/novawallet/Common/Services/Web3Name/Web3NameService.swift +++ b/novawallet/Common/Services/Web3Name/Web3NameService.swift @@ -150,7 +150,7 @@ final class Web3NameService: AnyCancellableCleaning { } guard let recipients = response.first(where: { - $0.key.match(chainId: chain.chainId, token: caip19Token) + $0.key.match(chain: chain, token: caip19Token) })?.value else { throw Web3NameServiceError.serviceNotFound(name, chain.name) } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift index 45d545738d..6bfc47cdca 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectModelFactory.swift @@ -90,8 +90,16 @@ enum WalletConnectModelFactory { do { let caip2ChainId = try Caip2.ChainId(raw: blockchain.absoluteString) + let optChainId = knownChainIds.first { chainId in + guard let chain = chainsStore.getChain(for: chainId) else { + return false + } + + return caip2ChainId.match(chain) + } + if - let chainId = knownChainIds.first(where: { caip2ChainId.match($0) }), + let chainId = optChainId, let chain = chainsStore.getChain(for: chainId) { return result.adding(chain: chain, blockchain: blockchain) } else { From 69e0b3a928d8e8620dfa981c88ea34a7da1d0112 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 1 May 2023 13:39:36 +0500 Subject: [PATCH 15/54] refactor dapp confirm --- Podfile | 3 +- Podfile.lock | 8 +- novawallet.xcodeproj/project.pbxproj | 16 + .../Common/Model/CAIP/CAIP-2/Caip2.swift | 18 + .../BaseExtrinsicOperationFactory.swift | 342 ++++++++++++++++++ .../ExtrinsicBuilderOperationFactory.swift | 48 +++ .../Substrate/ExtrinsicOperationFactory.swift | 322 +---------------- .../DAppBrowserAuthorizedState.swift | 4 +- .../Transports/DAppMetamaskTransport.swift | 24 +- .../DAppEthereumConfirmInteractor.swift | 29 +- .../DAppEthereumSignBytesInteractor.swift | 27 +- ...pOperationConfirmInteractor+Protocol.swift | 46 +-- .../DAppOperationConfirmInteractor.swift | 126 ++----- .../DAppOperationConfirmPresenter.swift | 3 +- .../DAppOperationConfirmProtocols.swift | 3 +- .../DAppOperationConfirmViewFactory.swift | 86 +++-- .../DAppSignBytesConfirmInteractor.swift | 21 +- ...DAppExtrinsicBuilderOperationFactory.swift | 123 +++++++ .../Model/DAppOperationConfirmModel.swift | 3 - .../Model/DAppOperationRequest.swift | 1 + ...DAppOperationConfirmViewModelFactory.swift | 20 +- .../Modules/DApp/Model/DAppSigningType.swift | 6 +- .../Modules/DApp/Model/DAppUnknownChain.swift | 40 ++ .../MetamaskProtocol/MetamaskChain.swift | 41 +-- .../Model/WalletConnectSignModelFactory.swift | 22 +- .../States/WalletConnectStateNewMessage.swift | 9 + 26 files changed, 789 insertions(+), 602 deletions(-) create mode 100644 novawallet/Common/Services/ExtrinsicService/Substrate/BaseExtrinsicOperationFactory.swift create mode 100644 novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicBuilderOperationFactory.swift create mode 100644 novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift create mode 100644 novawallet/Modules/DApp/Model/DAppUnknownChain.swift diff --git a/Podfile b/Podfile index 80b4b1b48a..bf9c59999b 100644 --- a/Podfile +++ b/Podfile @@ -24,7 +24,8 @@ abstract_target 'novawalletAll' do pod 'CDMarkdownKit', :git => 'https://github.com/nova-wallet/CDMarkdownKit.git', :tag => '2.5.2' pod 'web3swift', :git => 'https://github.com/web3swift-team/web3swift.git', :tag => '3.0.6' pod 'WalletConnectSwiftV2' - + pod 'EthereumSignTypedDataUtil' + target 'novawalletTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index fb07d330db..f63e2b6b14 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,6 +15,9 @@ PODS: - Cuckoo (1.7.1): - Cuckoo/Swift (= 1.7.1) - Cuckoo/Swift (1.7.1) + - EthereumSignTypedDataUtil (0.1.2): + - BigInt (~> 5.0) + - CryptoSwift (~> 1.4) - FireMock (3.1) - IrohaCrypto/BIP39 (0.9.0): - IrohaCrypto/Common @@ -151,6 +154,7 @@ DEPENDENCIES: - CDMarkdownKit (from `https://github.com/nova-wallet/CDMarkdownKit.git`, tag `2.5.2`) - CommonWallet/Core (from `https://github.com/ERussel/Capital-iOS.git`, tag `1.16.0`) - Cuckoo + - EthereumSignTypedDataUtil - FireMock - Kingfisher - R.swift @@ -177,6 +181,7 @@ SPEC REPOS: - CocoaLumberjack - CryptoSwift - Cuckoo + - EthereumSignTypedDataUtil - FireMock - IrohaCrypto - keccak.c @@ -257,6 +262,7 @@ SPEC CHECKSUMS: CommonWallet: 59fdbf5511d6fcdc38496db4baf425dd0cf29274 CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082 Cuckoo: 9e258d68137c411df47c6390f72901d5276b4f03 + EthereumSignTypedDataUtil: ae4e33b21e51ee046a86a65b02843090b8bbd3f9 FireMock: 3eed872059c12f94855413347da83b9d6d1a6fac IrohaCrypto: 6be75a4268cd1f5cec4231c6d3f95cb03f723fd3 keccak.c: 859583afdaccb4e4fcc0f0096064d101580313f4 @@ -285,6 +291,6 @@ SPEC CHECKSUMS: web3swift: 944e76579b953a7b7e81dbb351c6dc0ed1defe63 xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 -PODFILE CHECKSUM: 507fd2585158ad3958f09de38c7a627e160b973f +PODFILE CHECKSUM: 045fa3d2044604370165702e5bf8e46facda88d9 COCOAPODS: 1.11.3 diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index dd55c05241..5a08046655 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -2369,6 +2369,10 @@ 84E8AC7127BB949300402635 /* RMRKNftV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7027BB949300402635 /* RMRKNftV1.swift */; }; 84E8AC7527BB975700402635 /* RMRKV1OperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7427BB975700402635 /* RMRKV1OperationFactory.swift */; }; 84E8AC7727BBC8E400402635 /* NFTIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7627BBC8E400402635 /* NFTIntegrationTests.swift */; }; + 84E8BA0729FE888C00FD9F40 /* DAppUnknownChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0629FE888C00FD9F40 /* DAppUnknownChain.swift */; }; + 84E8BA0929FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0829FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift */; }; + 84E8BA0B29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0A29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift */; }; + 84E8BA0D29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0C29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift */; }; 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E90BA028D0B51000529633 /* CheckboxControlView.swift */; }; 84E9A05028F000AB00551DC4 /* ReferendumMetadataLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */; }; 84EA0B2A25E579DF00AFB0DC /* AssetBalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */; }; @@ -5816,6 +5820,10 @@ 84E8AC7027BB949300402635 /* RMRKNftV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMRKNftV1.swift; sourceTree = ""; }; 84E8AC7427BB975700402635 /* RMRKV1OperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMRKV1OperationFactory.swift; sourceTree = ""; }; 84E8AC7627BBC8E400402635 /* NFTIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTIntegrationTests.swift; sourceTree = ""; }; + 84E8BA0629FE888C00FD9F40 /* DAppUnknownChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppUnknownChain.swift; sourceTree = ""; }; + 84E8BA0829FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtrinsicBuilderOperationFactory.swift; sourceTree = ""; }; + 84E8BA0A29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppExtrinsicBuilderOperationFactory.swift; sourceTree = ""; }; + 84E8BA0C29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseExtrinsicOperationFactory.swift; sourceTree = ""; }; 84E90BA028D0B51000529633 /* CheckboxControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxControlView.swift; sourceTree = ""; }; 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataLocal.swift; sourceTree = ""; }; 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetBalanceViewModel.swift; sourceTree = ""; }; @@ -8005,6 +8013,7 @@ 840D92A2278EDB2E0007B979 /* DAppParsedCall.swift */, 843612C0278FE62900DC739E /* DAppOperationConfirmInteractorError.swift */, 843612C62790032400DC739E /* DAppOperationProcessedResult.swift */, + 84E8BA0A29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift */, ); path = Model; sourceTree = ""; @@ -12032,6 +12041,8 @@ 8433B34929B63661005E5D0F /* ExtrinsicSplitter.swift */, 84FC190A29B7DB9F00BCCAA5 /* ExtrinsicServiceTypes.swift */, 844A539629BF606D00C77111 /* BlockchainWeightFactory.swift */, + 84E8BA0829FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift */, + 84E8BA0C29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift */, ); path = Substrate; sourceTree = ""; @@ -13905,6 +13916,7 @@ 84D17ED528053D6D00F7BAFF /* DAppFavorite.swift */, 84D17ED728053E3200F7BAFF /* DAppFavoriteMapper.swift */, 8804AD8C295B7735001C4E09 /* DAppGlobalSettingsMapper.swift */, + 84E8BA0629FE888C00FD9F40 /* DAppUnknownChain.swift */, ); path = Model; sourceTree = ""; @@ -17011,6 +17023,7 @@ 849E07F6284A114B00DE0440 /* ParaStkScheduledRequestsMapper.swift in Sources */, 84CFF1EB26526FBC00DB7CF7 /* StakingBondMoreConfirmViewModel.swift in Sources */, 84BF12A328D06604007AA576 /* ParaStkYieldBoostCommonInteractor.swift in Sources */, + 84E8BA0929FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift in Sources */, 8490142D24A935FE008F705E /* WebPresentable.swift in Sources */, 849D755D27577602007726C3 /* AccountExportPasswordViewLayout.swift in Sources */, 849628CB299684D70073F143 /* GovernanceYourDelegationGroup.swift in Sources */, @@ -19076,6 +19089,7 @@ 8849AD6C29C37EB800F4F7FF /* Web3NameReceipientView.swift in Sources */, 4040F1394A73AC2FE598242C /* ControllerAccountConfirmationViewController.swift in Sources */, C6E5671768DA68535DA5B1C7 /* ControllerAccountConfirmationViewFactory.swift in Sources */, + 84E8BA0D29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift in Sources */, 270C21973CB61F0BF3D2D1E3 /* CrowdloanListProtocols.swift in Sources */, 6D61E43A79BDF5EA6CA9E85D /* CrowdloanListWireframe.swift in Sources */, A090FF206B56A0E465C62072 /* CrowdloanListPresenter.swift in Sources */, @@ -19539,6 +19553,7 @@ 8448F7A8288401A60080CEA9 /* AssetListInitState.swift in Sources */, 0909E06D5D06569554F70DD8 /* AssetsSearchInteractor.swift in Sources */, 84C2063828D1BF20006D0D52 /* AccountDetailsSelectionDecorator.swift in Sources */, + 84E8BA0B29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift in Sources */, 8434B602298D363C00FACF4C /* GovernanceAddDelegationTracksViewController.swift in Sources */, 2F21134DE157A4B98ED309E2 /* AssetsSearchViewController.swift in Sources */, 8407716828CE8A1B007DBD24 /* ParaStkYieldBoostSetupInteractor+Children.swift in Sources */, @@ -19694,6 +19709,7 @@ CE4C1344F03A5132C601A594 /* LocksViewController.swift in Sources */, 8A23DD1F4146639EA2F7AEF6 /* LocksViewFactory.swift in Sources */, C0A7710415B9C9BA496320E7 /* ParaStkYieldBoostSetupProtocols.swift in Sources */, + 84E8BA0729FE888C00FD9F40 /* DAppUnknownChain.swift in Sources */, 7E5ACF8DDF17C054E6E1B3D5 /* ParaStkYieldBoostSetupWireframe.swift in Sources */, 7C0135CA49EF6B535030643E /* ParaStkYieldBoostSetupPresenter.swift in Sources */, 21B297239CC294307EF20B58 /* ParaStkYieldBoostSetupInteractor.swift in Sources */, diff --git a/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift b/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift index 9f1ba3f54c..3ae2dae382 100644 --- a/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift +++ b/novawallet/Common/Model/CAIP/CAIP-2/Caip2.swift @@ -25,6 +25,15 @@ extension Caip2 { namespace = parsedNamespace reference = parsedReference } + + init(namespace: String, reference: String) { + self.namespace = namespace + self.reference = reference + } + + var rawString: String { + namespace + String(String.Separator.colon.rawValue) + reference + } } } @@ -43,6 +52,15 @@ extension Caip2 { return false } } + + var rawChainId: String { + switch self { + case let .polkadot(genesisHash): + return ChainId(namespace: "polkadot", reference: genesisHash).rawString + case let .eip155(id): + return ChainId(namespace: "eip155", reference: String(id)).rawString + } + } } } diff --git a/novawallet/Common/Services/ExtrinsicService/Substrate/BaseExtrinsicOperationFactory.swift b/novawallet/Common/Services/ExtrinsicService/Substrate/BaseExtrinsicOperationFactory.swift new file mode 100644 index 0000000000..b17b6a61ef --- /dev/null +++ b/novawallet/Common/Services/ExtrinsicService/Substrate/BaseExtrinsicOperationFactory.swift @@ -0,0 +1,342 @@ +import Foundation +import SubstrateSdk +import RobinHood +import BigInt + +class BaseExtrinsicOperationFactory { + let runtimeRegistry: RuntimeCodingServiceProtocol + let engine: JSONRPCEngine + let operationManager: OperationManagerProtocol + + init( + runtimeRegistry: RuntimeCodingServiceProtocol, + engine: JSONRPCEngine, + operationManager: OperationManagerProtocol + ) { + self.runtimeRegistry = runtimeRegistry + self.engine = engine + self.operationManager = operationManager + } + + func createDummySigner() throws -> DummySigner { + fatalError("Subclass must override this method") + } + + func createExtrinsicWrapper( + customClosure _: @escaping ExtrinsicBuilderIndexedClosure, + indexes _: [Int], + signingClosure _: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper<[Data]> { + fatalError("Subclass must override this method") + } + + private func createTipInclusionOperation( + dependingOn infoOperation: BaseOperation, + extrinsicData: Data, + codingFactory: RuntimeCoderFactoryProtocol + ) -> BaseOperation { + ClosureOperation { + let info = try infoOperation.extractNoCancellableResultData() + + guard let baseFee = BigUInt(info.fee) else { + return info + } + + let decoder = try codingFactory.createDecoder(from: extrinsicData) + let context = codingFactory.createRuntimeJsonContext() + let decodedExtrinsic: Extrinsic = try decoder.read( + of: GenericType.extrinsic.name, + with: context.toRawContext() + ) + + if let tip = decodedExtrinsic.signature?.extra.getTip() { + let newFee = baseFee + tip + return RuntimeDispatchInfo( + fee: String(newFee), + weight: info.weight + ) + } else { + return info + } + } + } + + private func createStateCallFeeWrapper( + for factory: RuntimeCoderFactoryProtocol, + type: String, + extrinsic: Data, + connection: JSONRPCEngine + ) -> CompoundOperationWrapper { + let requestOperation = ClosureOperation { + let lengthEncoder = factory.createEncoder() + try lengthEncoder.appendU32(json: .stringValue(String(extrinsic.count))) + let lengthInBytes = try lengthEncoder.encode() + + let totalBytes = extrinsic + lengthInBytes + + return StateCallRpc.Request(builtInFunction: StateCallRpc.feeBuiltIn) { container in + try container.encode(totalBytes.toHex(includePrefix: true)) + } + } + + let infoOperation = JSONRPCOperation( + engine: connection, + method: StateCallRpc.method + ) + + infoOperation.configurationBlock = { + do { + infoOperation.parameters = try requestOperation.extractNoCancellableResultData() + } catch { + infoOperation.result = .failure(error) + } + } + + infoOperation.addDependency(requestOperation) + + let mapOperation = ClosureOperation { + let result = try infoOperation.extractNoCancellableResultData() + let resultData = try Data(hexString: result) + let decoder = try factory.createDecoder(from: resultData) + let remoteModel = try decoder.read(type: type).map( + to: RemoteRuntimeDispatchInfo.self, + with: factory.createRuntimeJsonContext().toRawContext() + ) + + return .init(fee: String(remoteModel.fee), weight: remoteModel.weight) + } + + mapOperation.addDependency(infoOperation) + + return CompoundOperationWrapper( + targetOperation: mapOperation, + dependencies: [requestOperation, infoOperation] + ) + } + + private func createApiFeeWrapper( + extrinsic: Data, + connection: JSONRPCEngine + ) -> CompoundOperationWrapper { + let infoOperation = JSONRPCListOperation( + engine: connection, + method: RPCMethod.paymentInfo, + parameters: [extrinsic.toHex(includePrefix: true)] + ) + + return CompoundOperationWrapper(targetOperation: infoOperation) + } + + private func createFeeOperation( + dependingOn codingFactoryOperation: BaseOperation, + extrinsicOperation: BaseOperation<[Data]>, + connection: JSONRPCEngine + ) -> BaseOperation<[RuntimeDispatchInfo]> { + OperationCombiningService( + operationManager: operationManager + ) { + let coderFactory = try codingFactoryOperation.extractNoCancellableResultData() + let extrinsics = try extrinsicOperation.extractNoCancellableResultData() + + let feeType = StateCallRpc.feeResultType + let hasFeeType = coderFactory.hasType(for: feeType) + + let feeOperationWrappers: [CompoundOperationWrapper] = + extrinsics.map { extrinsicData in + let feeWrapper: CompoundOperationWrapper + + if hasFeeType { + feeWrapper = self.createStateCallFeeWrapper( + for: coderFactory, + type: feeType, + extrinsic: extrinsicData, + connection: connection + ) + } else { + feeWrapper = self.createApiFeeWrapper( + extrinsic: extrinsicData, + connection: connection + ) + } + + let tipInclusionOperation = self.createTipInclusionOperation( + dependingOn: feeWrapper.targetOperation, + extrinsicData: extrinsicData, + codingFactory: coderFactory + ) + + tipInclusionOperation.addDependency(feeWrapper.targetOperation) + + return CompoundOperationWrapper( + targetOperation: tipInclusionOperation, + dependencies: feeWrapper.allOperations + ) + } + + return feeOperationWrappers + }.longrunOperation() + } +} + +extension BaseExtrinsicOperationFactory: ExtrinsicOperationFactoryProtocol { + var connection: JSONRPCEngine { engine } + + func estimateFeeOperation( + _ closure: @escaping ExtrinsicBuilderIndexedClosure, + indexes: IndexSet + ) -> CompoundOperationWrapper { + do { + let signer = try createDummySigner() + + let signingClosure: (Data) throws -> Data = { data in + try signer.sign(data).rawData() + } + + let indexList = Array(indexes) + + let builderWrapper = createExtrinsicWrapper( + customClosure: closure, + indexes: indexList, + signingClosure: signingClosure + ) + + let coderFactoryOperation = runtimeRegistry.fetchCoderFactoryOperation() + + let feeOperation = createFeeOperation( + dependingOn: coderFactoryOperation, + extrinsicOperation: builderWrapper.targetOperation, + connection: connection + ) + + feeOperation.addDependency(coderFactoryOperation) + feeOperation.addDependency(builderWrapper.targetOperation) + + let wrapperOperation = ClosureOperation> { + do { + let results = try feeOperation.extractNoCancellableResultData() + + let indexedResults = zip(indexList, results).map { indexedResult in + FeeIndexedExtrinsicResult.IndexedResult( + index: indexedResult.0, + result: .success(indexedResult.1) + ) + } + + return .init(builderClosure: closure, results: indexedResults) + } catch { + let indexedResults = indexList.map { index in + FeeIndexedExtrinsicResult.IndexedResult(index: index, result: .failure(error)) + } + + return .init(builderClosure: closure, results: indexedResults) + } + } + + wrapperOperation.addDependency(feeOperation) + + return CompoundOperationWrapper( + targetOperation: wrapperOperation, + dependencies: builderWrapper.allOperations + [coderFactoryOperation, feeOperation] + ) + } catch { + return CompoundOperationWrapper.createWithError(error) + } + } + + func submit( + _ closure: @escaping ExtrinsicBuilderIndexedClosure, + signer: SigningWrapperProtocol, + indexes: IndexSet + ) -> CompoundOperationWrapper { + let signingClosure: (Data) throws -> Data = { data in + try signer.sign(data).rawData() + } + + let indexList = Array(indexes) + + let builderWrapper = createExtrinsicWrapper( + customClosure: closure, + indexes: indexList, + signingClosure: signingClosure + ) + + let submitOperationList: [JSONRPCListOperation] = + indexList.map { index in + let submitOperation = JSONRPCListOperation( + engine: engine, + method: RPCMethod.submitExtrinsic + ) + + submitOperation.configurationBlock = { + do { + let extrinsics = try builderWrapper.targetOperation.extractNoCancellableResultData() + let extrinsic = extrinsics[index].toHex(includePrefix: true) + + submitOperation.parameters = [extrinsic] + } catch { + submitOperation.result = .failure(error) + } + } + + submitOperation.addDependency(builderWrapper.targetOperation) + + return submitOperation + } + + let wrapperOperation = ClosureOperation { + let indexedResults = zip(indexList, submitOperationList).map { indexedOperation in + if let result = indexedOperation.1.result { + return SubmitIndexedExtrinsicResult.IndexedResult(index: indexedOperation.0, result: result) + } else { + return SubmitIndexedExtrinsicResult.IndexedResult( + index: indexedOperation.0, + result: .failure(BaseOperationError.parentOperationCancelled) + ) + } + } + + return .init(builderClosure: closure, results: indexedResults) + } + + submitOperationList.forEach { submitOperation in + wrapperOperation.addDependency(submitOperation) + } + + return CompoundOperationWrapper( + targetOperation: wrapperOperation, + dependencies: builderWrapper.allOperations + submitOperationList + ) + } + + func buildExtrinsic( + _ closure: @escaping ExtrinsicBuilderClosure, + signer: SigningWrapperProtocol + ) -> CompoundOperationWrapper { + let wrapperClosure: ExtrinsicBuilderIndexedClosure = { builder, _ in + try closure(builder) + } + + let signingClosure: (Data) throws -> Data = { data in + try signer.sign(data).rawData() + } + + let builderWrapper = createExtrinsicWrapper( + customClosure: wrapperClosure, + indexes: [0], + signingClosure: signingClosure + ) + + let resOperation: ClosureOperation = ClosureOperation { + let extrinsic = try builderWrapper.targetOperation.extractNoCancellableResultData().first! + return extrinsic.toHex(includePrefix: true) + } + builderWrapper.allOperations.forEach { + resOperation.addDependency($0) + } + + return CompoundOperationWrapper( + targetOperation: resOperation, + dependencies: builderWrapper.allOperations + ) + } +} diff --git a/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicBuilderOperationFactory.swift b/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicBuilderOperationFactory.swift new file mode 100644 index 0000000000..6c8cdab80b --- /dev/null +++ b/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicBuilderOperationFactory.swift @@ -0,0 +1,48 @@ +import Foundation +import SubstrateSdk +import RobinHood + +protocol ExtrinsicBuilderOperationFactoryProtocol { + func createWrapper( + customClosure: @escaping ExtrinsicBuilderIndexedClosure, + indexes: [Int], + signingClosure: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper<[Data]> + + func createDummySigner() throws -> DummySigner +} + +final class ExtrinsicProxyOperationFactory: BaseExtrinsicOperationFactory { + let proxy: ExtrinsicBuilderOperationFactoryProtocol + + init( + proxy: ExtrinsicBuilderOperationFactoryProtocol, + runtimeRegistry: RuntimeCodingServiceProtocol, + engine: JSONRPCEngine, + operationManager: OperationManagerProtocol + ) { + self.proxy = proxy + + super.init( + runtimeRegistry: runtimeRegistry, + engine: engine, + operationManager: operationManager + ) + } + + override func createDummySigner() throws -> DummySigner { + try proxy.createDummySigner() + } + + override func createExtrinsicWrapper( + customClosure: @escaping ExtrinsicBuilderIndexedClosure, + indexes: [Int], + signingClosure: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper<[Data]> { + proxy.createWrapper( + customClosure: customClosure, + indexes: indexes, + signingClosure: signingClosure + ) + } +} diff --git a/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicOperationFactory.swift b/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicOperationFactory.swift index 386032aade..8d66682b0a 100644 --- a/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicOperationFactory.swift +++ b/novawallet/Common/Services/ExtrinsicService/Substrate/ExtrinsicOperationFactory.swift @@ -101,16 +101,13 @@ extension ExtrinsicOperationFactoryProtocol { } } -final class ExtrinsicOperationFactory { +final class ExtrinsicOperationFactory: BaseExtrinsicOperationFactory { let accountId: AccountId let cryptoType: MultiassetCryptoType let signaturePayloadFormat: ExtrinsicSignaturePayloadFormat let chain: ChainModel - let runtimeRegistry: RuntimeCodingServiceProtocol let customExtensions: [ExtrinsicExtension] - let engine: JSONRPCEngine let eraOperationFactory: ExtrinsicEraOperationFactoryProtocol - let operationManager: OperationManagerProtocol init( accountId: AccountId, @@ -127,11 +124,14 @@ final class ExtrinsicOperationFactory { self.chain = chain self.cryptoType = cryptoType self.signaturePayloadFormat = signaturePayloadFormat - self.runtimeRegistry = runtimeRegistry self.customExtensions = customExtensions - self.engine = engine self.eraOperationFactory = eraOperationFactory - self.operationManager = operationManager + + super.init( + runtimeRegistry: runtimeRegistry, + engine: engine, + operationManager: operationManager + ) } private func createNonceOperation() -> BaseOperation { @@ -168,7 +168,7 @@ final class ExtrinsicOperationFactory { return requestOperation } - private func createExtrinsicOperation( + override func createExtrinsicWrapper( customClosure: @escaping ExtrinsicBuilderIndexedClosure, indexes: [Int], signingClosure: @escaping (Data) throws -> Data @@ -247,309 +247,7 @@ final class ExtrinsicOperationFactory { return CompoundOperationWrapper(targetOperation: extrinsicsOperation, dependencies: dependencies) } - private func createTipInclusionOperation( - dependingOn infoOperation: BaseOperation, - extrinsicData: Data, - codingFactory: RuntimeCoderFactoryProtocol - ) -> BaseOperation { - ClosureOperation { - let info = try infoOperation.extractNoCancellableResultData() - - guard let baseFee = BigUInt(info.fee) else { - return info - } - - let decoder = try codingFactory.createDecoder(from: extrinsicData) - let context = codingFactory.createRuntimeJsonContext() - let decodedExtrinsic: Extrinsic = try decoder.read( - of: GenericType.extrinsic.name, - with: context.toRawContext() - ) - - if let tip = decodedExtrinsic.signature?.extra.getTip() { - let newFee = baseFee + tip - return RuntimeDispatchInfo( - fee: String(newFee), - weight: info.weight - ) - } else { - return info - } - } - } - - private func createStateCallFeeWrapper( - for factory: RuntimeCoderFactoryProtocol, - type: String, - extrinsic: Data, - connection: JSONRPCEngine - ) -> CompoundOperationWrapper { - let requestOperation = ClosureOperation { - let lengthEncoder = factory.createEncoder() - try lengthEncoder.appendU32(json: .stringValue(String(extrinsic.count))) - let lengthInBytes = try lengthEncoder.encode() - - let totalBytes = extrinsic + lengthInBytes - - return StateCallRpc.Request(builtInFunction: StateCallRpc.feeBuiltIn) { container in - try container.encode(totalBytes.toHex(includePrefix: true)) - } - } - - let infoOperation = JSONRPCOperation( - engine: connection, - method: StateCallRpc.method - ) - - infoOperation.configurationBlock = { - do { - infoOperation.parameters = try requestOperation.extractNoCancellableResultData() - } catch { - infoOperation.result = .failure(error) - } - } - - infoOperation.addDependency(requestOperation) - - let mapOperation = ClosureOperation { - let result = try infoOperation.extractNoCancellableResultData() - let resultData = try Data(hexString: result) - let decoder = try factory.createDecoder(from: resultData) - let remoteModel = try decoder.read(type: type).map( - to: RemoteRuntimeDispatchInfo.self, - with: factory.createRuntimeJsonContext().toRawContext() - ) - - return .init(fee: String(remoteModel.fee), weight: remoteModel.weight) - } - - mapOperation.addDependency(infoOperation) - - return CompoundOperationWrapper( - targetOperation: mapOperation, - dependencies: [requestOperation, infoOperation] - ) - } - - private func createApiFeeWrapper( - extrinsic: Data, - connection: JSONRPCEngine - ) -> CompoundOperationWrapper { - let infoOperation = JSONRPCListOperation( - engine: connection, - method: RPCMethod.paymentInfo, - parameters: [extrinsic.toHex(includePrefix: true)] - ) - - return CompoundOperationWrapper(targetOperation: infoOperation) - } - - private func createFeeOperation( - dependingOn codingFactoryOperation: BaseOperation, - extrinsicOperation: BaseOperation<[Data]>, - connection: JSONRPCEngine - ) -> BaseOperation<[RuntimeDispatchInfo]> { - OperationCombiningService( - operationManager: operationManager - ) { - let coderFactory = try codingFactoryOperation.extractNoCancellableResultData() - let extrinsics = try extrinsicOperation.extractNoCancellableResultData() - - let feeType = StateCallRpc.feeResultType - let hasFeeType = coderFactory.hasType(for: feeType) - - let feeOperationWrappers: [CompoundOperationWrapper] = - extrinsics.map { extrinsicData in - let feeWrapper: CompoundOperationWrapper - - if hasFeeType { - feeWrapper = self.createStateCallFeeWrapper( - for: coderFactory, - type: feeType, - extrinsic: extrinsicData, - connection: connection - ) - } else { - feeWrapper = self.createApiFeeWrapper( - extrinsic: extrinsicData, - connection: connection - ) - } - - let tipInclusionOperation = self.createTipInclusionOperation( - dependingOn: feeWrapper.targetOperation, - extrinsicData: extrinsicData, - codingFactory: coderFactory - ) - - tipInclusionOperation.addDependency(feeWrapper.targetOperation) - - return CompoundOperationWrapper( - targetOperation: tipInclusionOperation, - dependencies: feeWrapper.allOperations - ) - } - - return feeOperationWrappers - }.longrunOperation() - } -} - -extension ExtrinsicOperationFactory: ExtrinsicOperationFactoryProtocol { - var connection: JSONRPCEngine { engine } - - func estimateFeeOperation( - _ closure: @escaping ExtrinsicBuilderIndexedClosure, - indexes: IndexSet - ) -> CompoundOperationWrapper { - let currentCryptoType = cryptoType - - let signingClosure: (Data) throws -> Data = { data in - try DummySigner(cryptoType: currentCryptoType).sign(data).rawData() - } - - let indexList = Array(indexes) - - let builderWrapper = createExtrinsicOperation( - customClosure: closure, - indexes: indexList, - signingClosure: signingClosure - ) - - let coderFactoryOperation = runtimeRegistry.fetchCoderFactoryOperation() - - let feeOperation = createFeeOperation( - dependingOn: coderFactoryOperation, - extrinsicOperation: builderWrapper.targetOperation, - connection: connection - ) - - feeOperation.addDependency(coderFactoryOperation) - feeOperation.addDependency(builderWrapper.targetOperation) - - let wrapperOperation = ClosureOperation> { - do { - let results = try feeOperation.extractNoCancellableResultData() - - let indexedResults = zip(indexList, results).map { indexedResult in - FeeIndexedExtrinsicResult.IndexedResult( - index: indexedResult.0, - result: .success(indexedResult.1) - ) - } - - return .init(builderClosure: closure, results: indexedResults) - } catch { - let indexedResults = indexList.map { index in - FeeIndexedExtrinsicResult.IndexedResult(index: index, result: .failure(error)) - } - - return .init(builderClosure: closure, results: indexedResults) - } - } - - wrapperOperation.addDependency(feeOperation) - - return CompoundOperationWrapper( - targetOperation: wrapperOperation, - dependencies: builderWrapper.allOperations + [coderFactoryOperation, feeOperation] - ) - } - - func submit( - _ closure: @escaping ExtrinsicBuilderIndexedClosure, - signer: SigningWrapperProtocol, - indexes: IndexSet - ) -> CompoundOperationWrapper { - let signingClosure: (Data) throws -> Data = { data in - try signer.sign(data).rawData() - } - - let indexList = Array(indexes) - - let builderWrapper = createExtrinsicOperation( - customClosure: closure, - indexes: indexList, - signingClosure: signingClosure - ) - - let submitOperationList: [JSONRPCListOperation] = - indexList.map { index in - let submitOperation = JSONRPCListOperation( - engine: engine, - method: RPCMethod.submitExtrinsic - ) - - submitOperation.configurationBlock = { - do { - let extrinsics = try builderWrapper.targetOperation.extractNoCancellableResultData() - let extrinsic = extrinsics[index].toHex(includePrefix: true) - - submitOperation.parameters = [extrinsic] - } catch { - submitOperation.result = .failure(error) - } - } - - submitOperation.addDependency(builderWrapper.targetOperation) - - return submitOperation - } - - let wrapperOperation = ClosureOperation { - let indexedResults = zip(indexList, submitOperationList).map { indexedOperation in - if let result = indexedOperation.1.result { - return SubmitIndexedExtrinsicResult.IndexedResult(index: indexedOperation.0, result: result) - } else { - return SubmitIndexedExtrinsicResult.IndexedResult( - index: indexedOperation.0, - result: .failure(BaseOperationError.parentOperationCancelled) - ) - } - } - - return .init(builderClosure: closure, results: indexedResults) - } - - submitOperationList.forEach { submitOperation in - wrapperOperation.addDependency(submitOperation) - } - - return CompoundOperationWrapper( - targetOperation: wrapperOperation, - dependencies: builderWrapper.allOperations + submitOperationList - ) - } - - func buildExtrinsic( - _ closure: @escaping ExtrinsicBuilderClosure, - signer: SigningWrapperProtocol - ) -> CompoundOperationWrapper { - let wrapperClosure: ExtrinsicBuilderIndexedClosure = { builder, _ in - try closure(builder) - } - - let signingClosure: (Data) throws -> Data = { data in - try signer.sign(data).rawData() - } - - let builderWrapper = createExtrinsicOperation( - customClosure: wrapperClosure, - indexes: [0], - signingClosure: signingClosure - ) - - let resOperation: ClosureOperation = ClosureOperation { - let extrinsic = try builderWrapper.targetOperation.extractNoCancellableResultData().first! - return extrinsic.toHex(includePrefix: true) - } - builderWrapper.allOperations.forEach { - resOperation.addDependency($0) - } - - return CompoundOperationWrapper( - targetOperation: resOperation, - dependencies: builderWrapper.allOperations - ) + override func createDummySigner() throws -> DummySigner { + try DummySigner(cryptoType: cryptoType) } } diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizedState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizedState.swift index 4bcfbf952d..7e1638d545 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizedState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizedState.swift @@ -61,7 +61,7 @@ final class DAppBrowserAuthorizedState: DAppBrowserBaseState { return } - guard dataSource.wallet.fetch(for: chain.accountRequest()) != nil else { + guard let accountId = dataSource.wallet.fetch(for: chain.accountRequest())?.accountId else { let error = DAppBrowserStateError.unexpected(reason: "no account for extrinsic chain") stateMachine?.emit(error: error, nextState: self) return @@ -71,6 +71,7 @@ final class DAppBrowserAuthorizedState: DAppBrowserBaseState { transportName: DAppTransports.polkadotExtension, identifier: message.identifier, wallet: dataSource.wallet, + accountId: accountId, dApp: message.url ?? "", dAppIcon: dataSource.dApp?.icon, operationData: jsonRequest @@ -117,6 +118,7 @@ final class DAppBrowserAuthorizedState: DAppBrowserBaseState { transportName: DAppTransports.polkadotExtension, identifier: message.identifier, wallet: dataSource.wallet, + accountId: accountId, dApp: message.url ?? "", dAppIcon: dataSource.dApp?.icon, operationData: .stringValue(payload.data) diff --git a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift index a2f0683447..1f3bb135f5 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift @@ -19,7 +19,8 @@ final class DAppMetamaskTransport { messageId: MetamaskMessage.Id, from signingOperation: JSON ) -> DAppOperationRequest? { - guard let dataSource = dataSource else { + guard let dataSource = dataSource, + let accountId = try? state?.fetchSelectedAddress(from: dataSource)?.toAccountId() else { return nil } @@ -27,6 +28,7 @@ final class DAppMetamaskTransport { transportName: DAppTransports.metamask, identifier: "\(messageId)", wallet: dataSource.wallet, + accountId: accountId, dApp: dataSource.dApp?.name ?? "", dAppIcon: dataSource.dApp?.icon, operationData: signingOperation @@ -81,14 +83,20 @@ extension DAppMetamaskTransport: DAppMetamaskStateMachineProtocol { state = nextState if let request = createConfirmationRequest(messageId: messageId, from: signingOperation) { - if signingOperation.stringValue == nil { - let type = DAppSigningType.ethereumSendTransaction(chain: nextState.chain) - delegate?.dAppTransport(self, didReceiveConfirmation: request, of: type) - } else if let accountId = try? state?.fetchSelectedAddress(from: dataSource)?.toAccountId() { - let type = DAppSigningType.ethereumBytes(chain: nextState.chain, accountId: accountId) - delegate?.dAppTransport(self, didReceiveConfirmation: request, of: type) + if + let chain = DAppEitherChain.createFromMetamask( + chain: nextState.chain, + dataSource: dataSource + ) { + if signingOperation.stringValue == nil { + let type = DAppSigningType.ethereumSendTransaction(chain: chain) + delegate?.dAppTransport(self, didReceiveConfirmation: request, of: type) + } else { + let type = DAppSigningType.ethereumBytes(chain: chain) + delegate?.dAppTransport(self, didReceiveConfirmation: request, of: type) + } } else { - let error = DAppBrowserStateError.unexpected(reason: "Can't find selected account id") + let error = DAppBrowserStateError.unexpected(reason: "Can't create chain") delegate?.dAppTransport(self, didReceive: error) } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift index 1d7cf7cbe8..ba69f2dddc 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift @@ -6,24 +6,24 @@ import SubstrateSdk final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { let request: DAppOperationRequest - let chain: MetamaskChain let ethereumOperationFactory: EthereumOperationFactoryProtocol let operationQueue: OperationQueue let signingWrapperFactory: SigningWrapperFactoryProtocol let serializationFactory: EthereumSerializationFactoryProtocol let shouldSendTransaction: Bool + let chainId: String init( + chainId: String, request: DAppOperationRequest, - chain: MetamaskChain, ethereumOperationFactory: EthereumOperationFactoryProtocol, operationQueue: OperationQueue, signingWrapperFactory: SigningWrapperFactoryProtocol, serializationFactory: EthereumSerializationFactoryProtocol, shouldSendTransaction: Bool ) { + self.chainId = chainId self.request = request - self.chain = chain self.ethereumOperationFactory = ethereumOperationFactory self.operationQueue = operationQueue self.signingWrapperFactory = signingWrapperFactory @@ -95,7 +95,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { } private func createSerializationOperation( - chain: MetamaskChain, + chainId: String, dependingOn transactionOperation: BaseOperation, signatureOperation: BaseOperation?, serializationFactory: EthereumSerializationFactoryProtocol @@ -114,7 +114,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { return try serializationFactory.serialize( transaction: transaction, - chainId: chain.chainId, + chainId: chainId, signature: maybeSignature ) } @@ -150,24 +150,13 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { return } - let networkUrl: URL? - - if let iconUrlString = chain.iconUrls?.first, let url = URL(string: iconUrlString) { - networkUrl = url - } else { - networkUrl = nil - } - let model = DAppOperationConfirmModel( accountName: request.wallet.name, walletIdenticon: request.wallet.walletIdenticonData(), chainAccountId: chainAccountId, chainAddress: transaction.from, - networkName: chain.chainName, - utilityAssetPrecision: chain.nativeCurrency.decimals, dApp: request.dApp, - dAppIcon: request.dAppIcon, - networkIcon: networkUrl + dAppIcon: request.dAppIcon ) presenter?.didReceive(modelResult: .success(model)) @@ -226,7 +215,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { private func confirmSend() { let transactionWrapper = createSigningTransactionWrapper(for: request) let signatureDataOperation = createSerializationOperation( - chain: chain, + chainId: chainId, dependingOn: transactionWrapper.targetOperation, signatureOperation: nil, serializationFactory: serializationFactory @@ -245,7 +234,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { signingOperation.addDependency(transactionWrapper.targetOperation) let serializationOperation = createSerializationOperation( - chain: chain, + chainId: chainId, dependingOn: transactionWrapper.targetOperation, signatureOperation: signingOperation, serializationFactory: serializationFactory @@ -288,7 +277,7 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { private func confirmSign() { let transactionWrapper = createSigningTransactionWrapper(for: request) let signatureDataOperation = createSerializationOperation( - chain: chain, + chainId: chainId, dependingOn: transactionWrapper.targetOperation, signatureOperation: nil, serializationFactory: serializationFactory diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift index 173ea56cf0..71b68ce0c4 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumSignBytesInteractor.swift @@ -4,54 +4,35 @@ import SoraKeystore final class DAppEthereumSignBytesInteractor: DAppOperationBaseInteractor { let request: DAppOperationRequest - let chain: MetamaskChain - let accountId: AccountId let signingWrapperFactory: SigningWrapperFactoryProtocol private(set) var account: MetaEthereumAccountResponse? init( request: DAppOperationRequest, - accountId: AccountId, - chain: MetamaskChain, signingWrapperFactory: SigningWrapperFactoryProtocol ) { self.request = request - self.accountId = accountId - self.chain = chain self.signingWrapperFactory = signingWrapperFactory } private func validateAndProvideConfirmationModel() { guard - let accountResponse = request.wallet.fetchEthereum(for: accountId), - let address = try? accountId.toAddress(using: .ethereum) else { + let accountResponse = request.wallet.fetchEthereum(for: request.accountId), + let address = try? request.accountId.toAddress(using: .ethereum) else { presenter?.didReceive(feeResult: .failure(ChainAccountFetchingError.accountNotExists)) return } account = accountResponse - let iconUrl: URL? - - if let urlString = chain.iconUrls?.first, let url = URL(string: urlString) { - iconUrl = url - } else { - iconUrl = nil - } - - let assetPrecision = chain.nativeCurrency.decimals - let confirmationModel = DAppOperationConfirmModel( accountName: request.wallet.name, walletIdenticon: request.wallet.walletIdenticonData(), - chainAccountId: accountId, + chainAccountId: request.accountId, chainAddress: address, - networkName: chain.chainName, - utilityAssetPrecision: assetPrecision, dApp: request.dApp, - dAppIcon: request.dAppIcon, - networkIcon: iconUrl + dAppIcon: request.dAppIcon ) presenter?.didReceive(modelResult: .success(confirmationModel)) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Protocol.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Protocol.swift index b5fe2ef681..29f9cd3a08 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Protocol.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor+Protocol.swift @@ -12,16 +12,16 @@ extension DAppOperationConfirmInteractor: DAppOperationConfirmInteractorInputPro } func confirm() { - guard signWrapper == nil, let result = processedResult else { + guard signWrapper == nil, let extrinsicFactory = extrinsicFactory else { return } let signer = signingWrapperFactory.createSigningWrapper( for: request.wallet.metaId, - accountResponse: result.account + accountResponse: extrinsicFactory.processedResult.account ) - let signWrapper = createSignatureOperation(for: result, signer: signer) + let signWrapper = createSignatureOperation(for: extrinsicFactory, signer: signer) self.signWrapper = signWrapper @@ -71,37 +71,22 @@ extension DAppOperationConfirmInteractor: DAppOperationConfirmInteractorInputPro } func estimateFee() { - guard feeWrapper == nil, let result = processedResult else { + guard feeWrapper == nil, let extrinsicFactory = extrinsicFactory else { return } - guard let signer = try? DummySigner(cryptoType: result.account.cryptoType) else { - return - } - - let builderWrapper = createFeePayloadOperation( - for: result, - signer: signer - ) - - let infoOperation = JSONRPCListOperation( + let operationFactory = ExtrinsicProxyOperationFactory( + proxy: extrinsicFactory, + runtimeRegistry: runtimeProvider, engine: connection, - method: RPCMethod.paymentInfo + operationManager: OperationManager(operationQueue: operationQueue) ) - infoOperation.configurationBlock = { - do { - let payload = try builderWrapper.targetOperation.extractNoCancellableResultData() - let extrinsic = payload.toHex(includePrefix: true) - infoOperation.parameters = [extrinsic] - } catch { - infoOperation.result = .failure(error) - } + let feeWrapper = operationFactory.estimateFeeOperation { builder in + builder } - infoOperation.addDependency(builderWrapper.targetOperation) - - infoOperation.completionBlock = { [weak self] in + feeWrapper.targetOperation.completionBlock = { [weak self] in DispatchQueue.main.async { guard self?.feeWrapper != nil else { return @@ -110,7 +95,7 @@ extension DAppOperationConfirmInteractor: DAppOperationConfirmInteractorInputPro self?.feeWrapper = nil do { - let info = try infoOperation.extractNoCancellableResultData() + let info = try feeWrapper.targetOperation.extractNoCancellableResultData() self?.presenter?.didReceive(feeResult: .success(info)) } catch { self?.presenter?.didReceive(feeResult: .failure(error)) @@ -118,18 +103,13 @@ extension DAppOperationConfirmInteractor: DAppOperationConfirmInteractorInputPro } } - let feeWrapper = CompoundOperationWrapper( - targetOperation: infoOperation, - dependencies: builderWrapper.allOperations - ) - self.feeWrapper = feeWrapper operationQueue.addOperations(feeWrapper.allOperations, waitUntilFinished: false) } func prepareTxDetails() { - guard let result = processedResult else { + guard let result = extrinsicFactory?.processedResult else { return } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift index cb646708a0..83cbfe7ca5 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift @@ -8,13 +8,13 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { let request: DAppOperationRequest let chain: ChainModel - let connection: ChainConnection + let connection: JSONRPCEngine let signingWrapperFactory: SigningWrapperFactoryProtocol let priceLocalSubscriptionFactory: PriceProviderFactoryProtocol let runtimeProvider: RuntimeProviderProtocol let operationQueue: OperationQueue - var processedResult: DAppOperationProcessedResult? + var extrinsicFactory: DAppExtrinsicBuilderOperationFactory? var priceProvider: StreamableProvider? var feeWrapper: CompoundOperationWrapper? @@ -24,7 +24,7 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { request: DAppOperationRequest, chain: ChainModel, runtimeProvider: RuntimeProviderProtocol, - connection: ChainConnection, + connection: JSONRPCEngine, signingWrapperFactory: SigningWrapperFactoryProtocol, priceProviderFactory: PriceProviderFactoryProtocol, currencyManager: CurrencyManagerProtocol, @@ -76,29 +76,18 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { } func completeSetup(for result: DAppOperationProcessedResult) { - processedResult = result - - let networkIconUrl: URL? - let assetPrecision: UInt16 - - if let asset = chain.utilityAssets().first { - networkIconUrl = asset.icon ?? chain.icon - assetPrecision = asset.precision - } else { - networkIconUrl = nil - assetPrecision = 0 - } + extrinsicFactory = DAppExtrinsicBuilderOperationFactory( + processedResult: result, + runtimeProvider: runtimeProvider + ) let confirmationModel = DAppOperationConfirmModel( accountName: request.wallet.name, walletIdenticon: request.wallet.walletIdenticonData(), chainAccountId: result.account.accountId, chainAddress: result.account.toAddress() ?? "", - networkName: chain.name, - utilityAssetPrecision: Int16(bitPattern: assetPrecision), dApp: request.dApp, - dAppIcon: request.dAppIcon, - networkIcon: networkIconUrl + dAppIcon: request.dAppIcon ) presenter?.didReceive(modelResult: .success(confirmationModel)) @@ -106,100 +95,31 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { estimateFee() } - func createBaseBuilderOperation( - for result: DAppOperationProcessedResult, - codingFactoryOperation: BaseOperation - ) -> BaseOperation { - ClosureOperation { - let runtimeContext = try codingFactoryOperation.extractNoCancellableResultData().createRuntimeJsonContext() - - let extrinsic = result.extrinsic - - let address = MultiAddress.accoundId(result.account.accountId) - - var builder: ExtrinsicBuilderProtocol = try ExtrinsicBuilder( - specVersion: extrinsic.specVersion, - transactionVersion: extrinsic.transactionVersion, - genesisHash: extrinsic.genesisHash - ) - .with(signaturePayloadFormat: result.account.type.signaturePayloadFormat) - .with(runtimeJsonContext: runtimeContext) - .with(address: address) - .with(nonce: UInt32(extrinsic.nonce)) - .with(era: extrinsic.era, blockHash: extrinsic.blockHash) - .adding(extrinsicExtension: ChargeAssetTxPayment()) - - builder = try result.extrinsic.method.accept(builder: builder) - - if extrinsic.tip > 0 { - builder = builder.with(tip: extrinsic.tip) - } - - return builder - } - } - - func createFeePayloadOperation( - for result: DAppOperationProcessedResult, - signer: SigningWrapperProtocol - ) -> CompoundOperationWrapper { - let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() - - let builderOperation = createBaseBuilderOperation( - for: result, - codingFactoryOperation: codingFactoryOperation - ) - - builderOperation.addDependency(codingFactoryOperation) - - let payloadOperation = ClosureOperation { - let builder = try builderOperation.extractNoCancellableResultData() - let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() - - return try builder.signing( - with: { try signer.sign($0).rawData() }, - chainFormat: result.account.chainFormat, - cryptoType: result.account.cryptoType, - codingFactory: codingFactory - ) - .build(encodingBy: codingFactory.createEncoder(), metadata: codingFactory.metadata) - } - - payloadOperation.addDependency(codingFactoryOperation) - payloadOperation.addDependency(builderOperation) - - return CompoundOperationWrapper( - targetOperation: payloadOperation, - dependencies: [codingFactoryOperation, builderOperation] - ) - } - func createSignatureOperation( - for result: DAppOperationProcessedResult, + for extrinsicFactory: DAppExtrinsicBuilderOperationFactory, signer: SigningWrapperProtocol ) -> CompoundOperationWrapper { - let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() - - let builderOperation = createBaseBuilderOperation( - for: result, - codingFactoryOperation: codingFactoryOperation + let signatureWrapper = extrinsicFactory.createWrapper( + customClosure: { builder, _ in builder }, + indexes: [0], + signingClosure: { data in + try signer.sign(data).rawData() + } ) - builderOperation.addDependency(codingFactoryOperation) + let codingFactoryOperation = extrinsicFactory.runtimeProvider.fetchCoderFactoryOperation() let signatureOperation = ClosureOperation { - let builder = try builderOperation.extractNoCancellableResultData() + let rawSignatures = try signatureWrapper.targetOperation.extractNoCancellableResultData() let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() - let rawSignature = try builder.buildRawSignature( - using: { try signer.sign($0).rawData() }, - encoder: codingFactory.createEncoder(), - metadata: codingFactory.metadata - ) + guard let rawSignature = rawSignatures.first else { + throw CommonError.dataCorruption + } let scaleEncoder = codingFactory.createEncoder() - switch result.account.cryptoType { + switch extrinsicFactory.processedResult.account.cryptoType { case .sr25519: try scaleEncoder.append( MultiSignature.sr25519(data: rawSignature), @@ -234,11 +154,11 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { } signatureOperation.addDependency(codingFactoryOperation) - signatureOperation.addDependency(builderOperation) + signatureOperation.addDependency(signatureWrapper.targetOperation) return CompoundOperationWrapper( targetOperation: signatureOperation, - dependencies: [codingFactoryOperation, builderOperation] + dependencies: [codingFactoryOperation] + signatureWrapper.allOperations ) } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift index 42062dd041..f48fc57a34 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift @@ -52,10 +52,9 @@ final class DAppOperationConfirmPresenter { return } - let assetPrecision = confirmationModel.utilityAssetPrecision guard let fee = BigUInt(feeModel.fee), - let feeDecimal = Decimal.fromSubstrateAmount(fee, precision: assetPrecision) else { + let feeDecimal = viewModelFactory.convertBalanceToDecimal(fee) else { view?.didReceive(feeViewModel: .loading) return } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift index 6a8bbbaf48..b10a64d099 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift @@ -28,7 +28,8 @@ protocol DAppOperationConfirmInteractorOutputProtocol: AnyObject { func didReceive(txDetailsResult: Result) } -protocol DAppOperationConfirmWireframeProtocol: AlertPresentable, ErrorPresentable, FeeRetryable, MessageSheetPresentable { +protocol DAppOperationConfirmWireframeProtocol: AlertPresentable, ErrorPresentable, FeeRetryable, + MessageSheetPresentable { func close(view: DAppOperationConfirmViewProtocol?) func showTxDetails(from view: DAppOperationConfirmViewProtocol?, json: JSON) } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift index a96799036c..c2caf78951 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift @@ -1,6 +1,8 @@ import Foundation import SoraKeystore import SoraFoundation +import SubstrateSdk +import BigInt struct DAppOperationConfirmViewFactory { static func createView( @@ -8,42 +10,44 @@ struct DAppOperationConfirmViewFactory { type: DAppSigningType, delegate: DAppOperationConfirmDelegate ) -> DAppOperationConfirmViewProtocol? { - let maybeInteractor: (DAppOperationBaseInteractor & DAppOperationConfirmInteractorInputProtocol)? - let maybeAssetInfo: AssetBalanceDisplayInfo? - switch type { case let .extrinsic(chain): - maybeAssetInfo = chain.utilityAssets().first?.displayInfo(with: chain.icon) - maybeInteractor = createExtrinsicInteractor(for: request, chain: chain) + let interactor = createExtrinsicInteractor(for: request, chain: chain) + return createView(for: interactor, chain: .left(chain), delegate: delegate) case let .bytes(chain): - maybeAssetInfo = chain.utilityAssets().first?.displayInfo(with: chain.icon) - maybeInteractor = createSignBytesInteractor(for: request, chain: chain) + let interactor = createSignBytesInteractor(for: request, chain: chain) + return createView(for: interactor, chain: .left(chain), delegate: delegate) case let .ethereumSendTransaction(chain): - maybeAssetInfo = chain.assetDisplayInfo - maybeInteractor = createEthereumInteractor( + let interactor = createEthereumInteractor( for: request, chain: chain, shouldSendTransaction: true ) + + return createView(for: interactor, chain: chain, delegate: delegate) case let .ethereumSignTransaction(chain): - maybeAssetInfo = chain.assetDisplayInfo - maybeInteractor = createEthereumInteractor( + let interactor = createEthereumInteractor( for: request, chain: chain, shouldSendTransaction: false ) - case let .ethereumBytes(chain, accountId): - maybeAssetInfo = chain.assetDisplayInfo - maybeInteractor = createEthereumPersonalSignInteractor( - for: request, - chain: chain, - accountId: accountId - ) + + return createView(for: interactor, chain: chain, delegate: delegate) + case let .ethereumBytes(chain): + let interactor = createEthereumPersonalSignInteractor(for: request) + return createView(for: interactor, chain: chain, delegate: delegate) } + } - guard let interactor = maybeInteractor, - let assetInfo = maybeAssetInfo, - let currencyManager = CurrencyManager.shared else { + private static func createView( + for interactor: (DAppOperationBaseInteractor & DAppOperationConfirmInteractorInputProtocol)?, + chain: DAppEitherChain, + delegate: DAppOperationConfirmDelegate + ) -> DAppOperationConfirmViewProtocol? { + guard + let interactor = interactor, + let assetInfo = chain.utilityAssetBalanceInfo, + let currencyManager = CurrencyManager.shared else { return nil } @@ -58,7 +62,7 @@ struct DAppOperationConfirmViewFactory { interactor: interactor, wireframe: wireframe, delegate: delegate, - viewModelFactory: DAppOperationConfirmViewModelFactory(), + viewModelFactory: DAppOperationConfirmViewModelFactory(chain: chain), balanceViewModelFactory: balanceViewModelFactory, localizationManager: LocalizationManager.shared, logger: Logger.shared @@ -113,18 +117,38 @@ struct DAppOperationConfirmViewFactory { private static func createEthereumInteractor( for request: DAppOperationRequest, - chain: MetamaskChain, + chain: Either, shouldSendTransaction: Bool ) -> DAppEthereumConfirmInteractor? { - guard let rpcUrlString = chain.rpcUrls.first, let rpcUrl = URL(string: rpcUrlString) else { - return nil + let operationFactory: EthereumOperationFactoryProtocol + let chainId: String + + switch chain { + case let .left(knownChain): + guard + let connection = ChainRegistryFacade.sharedRegistry.getOneShotConnection( + for: knownChain.chainId + ) else { + return nil + } + + operationFactory = EvmWebSocketOperationFactory(connection: connection) + chainId = BigUInt(knownChain.addressPrefix).toHexWithPrefix() + case let .right(unknownChain): + guard let connection = HTTPEngine( + urls: [unknownChain.rpcUrl], + operationQueue: OperationManagerFacade.sharedDefaultQueue + ) else { + return nil + } + + operationFactory = EvmWebSocketOperationFactory(connection: connection) + chainId = unknownChain.chainId } - let operationFactory = EthereumOperationFactory(node: rpcUrl) - return DAppEthereumConfirmInteractor( + chainId: chainId, request: request, - chain: chain, ethereumOperationFactory: operationFactory, operationQueue: OperationManagerFacade.sharedDefaultQueue, signingWrapperFactory: SigningWrapperFactory(keystore: Keychain()), @@ -134,14 +158,10 @@ struct DAppOperationConfirmViewFactory { } private static func createEthereumPersonalSignInteractor( - for request: DAppOperationRequest, - chain: MetamaskChain, - accountId: AccountId + for request: DAppOperationRequest ) -> DAppEthereumSignBytesInteractor { DAppEthereumSignBytesInteractor( request: request, - accountId: accountId, - chain: chain, signingWrapperFactory: SigningWrapperFactory() ) } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift index 52defddef3..30dd9d2eff 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppSignBytesConfirmInteractor.swift @@ -21,7 +21,10 @@ final class DAppSignBytesConfirmInteractor: DAppOperationBaseInteractor { private func validateAndProvideConfirmationModel() { guard - let accountResponse = request.wallet.fetch(for: chain.accountRequest()), + let accountResponse = request.wallet.fetchByAccountId( + request.accountId, + request: chain.accountRequest() + ), let chainAddress = accountResponse.toAddress() else { presenter?.didReceive(feeResult: .failure(ChainAccountFetchingError.accountNotExists)) return @@ -29,27 +32,13 @@ final class DAppSignBytesConfirmInteractor: DAppOperationBaseInteractor { account = accountResponse - let networkIconUrl: URL? - let assetPrecision: UInt16 - - if let asset = chain.utilityAssets().first { - networkIconUrl = asset.icon ?? chain.icon - assetPrecision = asset.precision - } else { - networkIconUrl = nil - assetPrecision = 0 - } - let confirmationModel = DAppOperationConfirmModel( accountName: request.wallet.name, walletIdenticon: request.wallet.walletIdenticonData(), chainAccountId: accountResponse.accountId, chainAddress: chainAddress, - networkName: chain.name, - utilityAssetPrecision: Int16(bitPattern: assetPrecision), dApp: request.dApp, - dAppIcon: request.dAppIcon, - networkIcon: networkIconUrl + dAppIcon: request.dAppIcon ) presenter?.didReceive(modelResult: .success(confirmationModel)) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift new file mode 100644 index 0000000000..dfb1048c67 --- /dev/null +++ b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift @@ -0,0 +1,123 @@ +import Foundation +import SubstrateSdk +import RobinHood + +final class DAppExtrinsicBuilderOperationFactory { + let processedResult: DAppOperationProcessedResult + let runtimeProvider: RuntimeCodingServiceProtocol + + init(processedResult: DAppOperationProcessedResult, runtimeProvider: RuntimeCodingServiceProtocol) { + self.processedResult = processedResult + self.runtimeProvider = runtimeProvider + } + + private func createBaseBuilderOperation( + for result: DAppOperationProcessedResult, + codingFactoryOperation: BaseOperation + ) -> BaseOperation { + ClosureOperation { + let runtimeContext = try codingFactoryOperation.extractNoCancellableResultData().createRuntimeJsonContext() + + let extrinsic = result.extrinsic + + let address = MultiAddress.accoundId(result.account.accountId) + + var builder: ExtrinsicBuilderProtocol = try ExtrinsicBuilder( + specVersion: extrinsic.specVersion, + transactionVersion: extrinsic.transactionVersion, + genesisHash: extrinsic.genesisHash + ) + .with(signaturePayloadFormat: result.account.type.signaturePayloadFormat) + .with(runtimeJsonContext: runtimeContext) + .with(address: address) + .with(nonce: UInt32(extrinsic.nonce)) + .with(era: extrinsic.era, blockHash: extrinsic.blockHash) + .adding(extrinsicExtension: ChargeAssetTxPayment()) + + builder = try result.extrinsic.method.accept(builder: builder) + + if extrinsic.tip > 0 { + builder = builder.with(tip: extrinsic.tip) + } + + return builder + } + } + + private func createRawSignatureOperation( + for result: DAppOperationProcessedResult, + signingClosure: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper { + let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + + let builderOperation = createBaseBuilderOperation( + for: result, + codingFactoryOperation: codingFactoryOperation + ) + + builderOperation.addDependency(codingFactoryOperation) + + let payloadOperation = ClosureOperation { + let builder = try builderOperation.extractNoCancellableResultData() + let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() + + return try builder.signing( + with: { try signingClosure($0) }, + chainFormat: result.account.chainFormat, + cryptoType: result.account.cryptoType, + codingFactory: codingFactory + ) + .build(encodingBy: codingFactory.createEncoder(), metadata: codingFactory.metadata) + } + + payloadOperation.addDependency(codingFactoryOperation) + payloadOperation.addDependency(builderOperation) + + return CompoundOperationWrapper( + targetOperation: payloadOperation, + dependencies: [codingFactoryOperation, builderOperation] + ) + } +} + +extension DAppExtrinsicBuilderOperationFactory: ExtrinsicBuilderOperationFactoryProtocol { + func createWrapper( + customClosure _: @escaping ExtrinsicBuilderIndexedClosure, + indexes _: [Int], + signingClosure: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper<[Data]> { + let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + + let builderOperation = createBaseBuilderOperation( + for: processedResult, + codingFactoryOperation: codingFactoryOperation + ) + + builderOperation.addDependency(codingFactoryOperation) + + let signatureWrapper = createRawSignatureOperation( + for: processedResult, + signingClosure: signingClosure + ) + + signatureWrapper.addDependency(operations: [builderOperation]) + + let mappingOperation = ClosureOperation<[Data]> { + let data = try signatureWrapper.targetOperation.extractNoCancellableResultData() + + return [data] + } + + mappingOperation.addDependency(signatureWrapper.targetOperation) + + let dependencies = [codingFactoryOperation, builderOperation] + signatureWrapper.allOperations + + let wrapper = CompoundOperationWrapper(targetOperation: mappingOperation, dependencies: dependencies) + + return wrapper + } + + func createDummySigner() throws -> DummySigner { + try DummySigner(cryptoType: processedResult.account.cryptoType) + } +} diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationConfirmModel.swift b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationConfirmModel.swift index 23aeb7b517..67201d6a8c 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationConfirmModel.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationConfirmModel.swift @@ -6,9 +6,6 @@ struct DAppOperationConfirmModel { let walletIdenticon: Data? let chainAccountId: AccountId let chainAddress: AccountAddress - let networkName: String - let utilityAssetPrecision: Int16 let dApp: String let dAppIcon: URL? - let networkIcon: URL? } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationRequest.swift b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationRequest.swift index 69c450c7f3..e8b8baf68d 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationRequest.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppOperationRequest.swift @@ -5,6 +5,7 @@ struct DAppOperationRequest { let transportName: String let identifier: String let wallet: MetaAccountModel + let accountId: AccountId let dApp: String let dAppIcon: URL? let operationData: JSON diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift index 8d5a260057..a6502cb186 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift @@ -1,11 +1,19 @@ import Foundation import SubstrateSdk +import BigInt protocol DAppOperationConfirmViewModelFactoryProtocol { func createViewModel(from model: DAppOperationConfirmModel) -> DAppOperationConfirmViewModel + func convertBalanceToDecimal(_ balance: BigUInt) -> Decimal? } final class DAppOperationConfirmViewModelFactory: DAppOperationConfirmViewModelFactoryProtocol { + let chain: DAppEitherChain + + init(chain: DAppEitherChain) { + self.chain = chain + } + func createViewModel(from model: DAppOperationConfirmModel) -> DAppOperationConfirmViewModel { let iconViewModel: ImageViewModelProtocol @@ -21,7 +29,7 @@ final class DAppOperationConfirmViewModelFactory: DAppOperationConfirmViewModelF let networkIcon: ImageViewModelProtocol? - if let networkIconUrl = model.networkIcon { + if let networkIconUrl = chain.networkIcon { networkIcon = RemoteImageViewModel(url: networkIconUrl) } else { networkIcon = nil @@ -33,8 +41,16 @@ final class DAppOperationConfirmViewModelFactory: DAppOperationConfirmViewModelF walletIcon: walletIcon, address: model.chainAddress.truncated, addressIcon: addressIcon, - networkName: model.networkName, + networkName: chain.networkName, networkIconViewModel: networkIcon ) } + + func convertBalanceToDecimal(_ balance: BigUInt) -> Decimal? { + guard let precision = chain.utilityAssetBalanceInfo?.assetPrecision else { + return nil + } + + return Decimal.fromSubstrateAmount(balance, precision: precision) + } } diff --git a/novawallet/Modules/DApp/Model/DAppSigningType.swift b/novawallet/Modules/DApp/Model/DAppSigningType.swift index 152e145e62..90201f7ec3 100644 --- a/novawallet/Modules/DApp/Model/DAppSigningType.swift +++ b/novawallet/Modules/DApp/Model/DAppSigningType.swift @@ -3,9 +3,9 @@ import Foundation enum DAppSigningType { case extrinsic(chain: ChainModel) case bytes(chain: ChainModel) - case ethereumSendTransaction(chain: MetamaskChain) - case ethereumSignTransaction(chain: MetamaskChain) - case ethereumBytes(chain: MetamaskChain, accountId: AccountId) + case ethereumSendTransaction(chain: DAppEitherChain) + case ethereumSignTransaction(chain: DAppEitherChain) + case ethereumBytes(chain: DAppEitherChain) var msgType: PolkadotExtensionMessage.MessageType? { switch self { diff --git a/novawallet/Modules/DApp/Model/DAppUnknownChain.swift b/novawallet/Modules/DApp/Model/DAppUnknownChain.swift new file mode 100644 index 0000000000..fdf47d6c7a --- /dev/null +++ b/novawallet/Modules/DApp/Model/DAppUnknownChain.swift @@ -0,0 +1,40 @@ +import Foundation + +struct DAppUnknownChain { + let chainId: String + let name: String + let icon: URL? + let assetDisplayInfo: AssetBalanceDisplayInfo + let rpcUrl: URL +} + +typealias DAppEitherChain = Either + +extension DAppEitherChain { + var networkName: String { + switch self { + case let .left(knowChain): + return knowChain.name + case let .right(unknownChain): + return unknownChain.name + } + } + + var networkIcon: URL? { + switch self { + case let .left(knowChain): + return knowChain.icon + case let .right(unknownChain): + return unknownChain.icon + } + } + + var utilityAssetBalanceInfo: AssetBalanceDisplayInfo? { + switch self { + case let .left(knowChain): + return knowChain.utilityAssetDisplayInfo() + case let .right(unknownChain): + return unknownChain.assetDisplayInfo + } + } +} diff --git a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift b/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift index 4049898038..f75f459576 100644 --- a/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift +++ b/novawallet/Modules/DApp/Model/MetamaskProtocol/MetamaskChain.swift @@ -55,30 +55,27 @@ extension MetamaskChain { } } -extension MetamaskChain { - init?(chain: ChainModel) { - guard let asset = chain.utilityAsset() else { - return nil - } - - chainId = BigUInt(chain.addressPrefix).toHexWithPrefix() - chainName = chain.name - nativeCurrency = .init( - name: asset.name ?? chain.name, - symbol: asset.symbol, - decimals: Int16(bitPattern: asset.precision) - ) - - // TODO: Fix node retrieval - - if let node = chain.nodes.first(where: { $0.url.hasPrefix(ConnectionNodeSchema.https) }) { - rpcUrls = [node.url] +extension DAppEitherChain { + static func createFromMetamask( + chain: MetamaskChain, + dataSource: DAppBrowserStateDataSource + ) -> DAppEitherChain? { + if let knownChain = dataSource.fetchChainByEthereumChainId(chain.chainId) { + return .left(knownChain) } else { - rpcUrls = [] - } + guard let rpcUrl = chain.rpcUrls.first.flatMap({ URL(string: $0) }) else { + return nil + } - blockExplorerUrls = nil + let unknownChain = DAppUnknownChain( + chainId: chain.chainId, + name: chain.chainName, + icon: chain.iconUrls?.first.flatMap { URL(string: $0) }, + assetDisplayInfo: chain.assetDisplayInfo, + rpcUrl: rpcUrl + ) - iconUrls = [chain.icon.absoluteString] + return .right(unknownChain) + } } } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift index c04b336deb..230b45bfae 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -243,7 +243,7 @@ extension WalletConnectSignModelFactory { } static func createSigningType( - for wallet: MetaAccountModel, + for _: MetaAccountModel, chain: ChainModel, method: WalletConnectMethod ) throws -> DAppSigningType { @@ -253,25 +253,11 @@ extension WalletConnectSignModelFactory { case .polkadotSignMessage: return .bytes(chain: chain) case .ethSendTransaction: - guard let metamaskChain = MetamaskChain(chain: chain) else { - throw CommonError.dataCorruption - } - - return .ethereumSendTransaction(chain: metamaskChain) + return .ethereumSendTransaction(chain: .left(chain)) case .ethSignTransaction: - guard let metamaskChain = MetamaskChain(chain: chain) else { - throw CommonError.dataCorruption - } - - return .ethereumSignTransaction(chain: metamaskChain) + return .ethereumSignTransaction(chain: .left(chain)) case .ethPersonalSign, .ethSignTypeData: - guard - let metamaskChain = MetamaskChain(chain: chain), - let accountId = wallet.fetch(for: chain.accountRequest())?.accountId else { - throw CommonError.dataCorruption - } - - return .ethereumBytes(chain: metamaskChain, accountId: accountId) + return .ethereumBytes(chain: .left(chain)) } } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index ac5564e72a..ebeae4763f 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -100,6 +100,14 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { return } + guard + let accountId = dataSource.walletSettings.value.fetch( + for: chain.accountRequest() + )?.accountId else { + rejectRequest(request: request) + return + } + do { let operationData = try WalletConnectSignModelFactory.createOperationData( for: dataSource.walletSettings.value, @@ -118,6 +126,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { transportName: DAppTransports.walletConnect, identifier: request.id.string, wallet: dataSource.walletSettings.value, + accountId: accountId, dApp: session?.peer.name ?? "", dAppIcon: session?.peer.icons.first.flatMap { URL(string: $0) }, operationData: operationData From 43a80f147dad45459e616f9e7e7d5954971a8b4a Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 1 May 2023 13:50:35 +0500 Subject: [PATCH 16/54] get rid of http ethereum operation --- novawallet.xcodeproj/project.pbxproj | 72 +++----- .../Ethereum/EthereumError+Presentable.swift | 10 -- .../EthereumOperationFactor+Protocol.swift | 160 ------------------ .../Ethereum/EthereumOperationFactory.swift | 26 --- .../Network/Ethereum/EthereumRpcRequest.swift | 29 ---- .../Ethereum/EthereumRpcResponse.swift | 20 --- 6 files changed, 28 insertions(+), 289 deletions(-) delete mode 100644 novawallet/Common/Network/Ethereum/EthereumError+Presentable.swift delete mode 100644 novawallet/Common/Network/Ethereum/EthereumOperationFactor+Protocol.swift delete mode 100644 novawallet/Common/Network/Ethereum/EthereumRpcRequest.swift delete mode 100644 novawallet/Common/Network/Ethereum/EthereumRpcResponse.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 5a08046655..a4902f6bd9 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1304,11 +1304,6 @@ 846E501527799B3E0049B659 /* DAppAuthViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E501427799B3E0049B659 /* DAppAuthViewModelFactory.swift */; }; 846E5018277AD3D50049B659 /* DAppList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E5017277AD3D50049B659 /* DAppList.swift */; }; 846F757E27B53F5100536547 /* DAppMetamaskSigningState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F757D27B53F5100536547 /* DAppMetamaskSigningState.swift */; }; - 846F758227B54D7A00536547 /* EthereumOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F758027B54D7A00536547 /* EthereumOperationFactory.swift */; }; - 846F758327B54D7A00536547 /* EthereumOperationFactor+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F758127B54D7A00536547 /* EthereumOperationFactor+Protocol.swift */; }; - 846F758527B54F8300536547 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F758427B54F8300536547 /* EthereumTransaction.swift */; }; - 846F758727B5550700536547 /* EthereumRpcRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F758627B5550700536547 /* EthereumRpcRequest.swift */; }; - 846F758927B5584100536547 /* EthereumRpcResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F758827B5584100536547 /* EthereumRpcResponse.swift */; }; 846FB02C28C74F9700CA5444 /* ParaStkYieldBoostState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846FB02B28C74F9700CA5444 /* ParaStkYieldBoostState.swift */; }; 846FB02E28C7524200CA5444 /* StakingParachainInteractor+Provide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846FB02D28C7524200CA5444 /* StakingParachainInteractor+Provide.swift */; }; 847012662982AE5700F29C87 /* StackTableView+Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847012652982AE5700F29C87 /* StackTableView+Cell.swift */; }; @@ -1867,7 +1862,6 @@ 84A70DA129CCEDC800C648AD /* GovV2SubsquareOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A70DA029CCEDC800C648AD /* GovV2SubsquareOperationFactory.swift */; }; 84A70DA329CCEDE800C648AD /* BaseSubsquareOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A70DA229CCEDE800C648AD /* BaseSubsquareOperationFactory.swift */; }; 84A7AC8029465BF9001A39CF /* TokenAddErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A7AC7F29465BF9001A39CF /* TokenAddErrorPresentable.swift */; }; - 84A7AC822946FC31001A39CF /* EthereumResultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A7AC812946FC31001A39CF /* EthereumResultParser.swift */; }; 84A8FD8E265FDA76002ADB58 /* CrowdloanContributionConfirmData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A8FD8D265FDA76002ADB58 /* CrowdloanContributionConfirmData.swift */; }; 84A90489288EA0E500DFC8E2 /* NoKeysCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A90488288EA0E500DFC8E2 /* NoKeysCommand.swift */; }; 84A9ECC1291292900094C763 /* GovernanceUnlockConfirmInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A9ECC0291292900094C763 /* GovernanceUnlockConfirmInteractorError.swift */; }; @@ -2174,7 +2168,6 @@ 84CEF28C290509D600BA25BB /* GovernanceVoteValidatingParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEF28B290509D600BA25BB /* GovernanceVoteValidatingParams.swift */; }; 84CEF28E29050A3300BA25BB /* DataValidationRunner+GovVote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEF28D29050A3300BA25BB /* DataValidationRunner+GovVote.swift */; }; 84CF00C327F6C1E4004DB322 /* CustomAssetMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CF00C227F6C1E4004DB322 /* CustomAssetMapper.swift */; }; - 84CFBC6528756CCB00E93EDA /* EthereumError+Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFBC6428756CCB00E93EDA /* EthereumError+Presentable.swift */; }; 84CFE441292B8CDA00CDDD7C /* EvmOnChainTransferSetupInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFE440292B8CDA00CDDD7C /* EvmOnChainTransferSetupInteractor.swift */; }; 84CFE443292B8F3E00CDDD7C /* TransferEvmOnChainConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFE442292B8F3E00CDDD7C /* TransferEvmOnChainConfirmInteractor.swift */; }; 84CFE448292B9CB100CDDD7C /* OnChainTransferBaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFE447292B9CB100CDDD7C /* OnChainTransferBaseInteractor.swift */; }; @@ -2229,7 +2222,6 @@ 84D2F45425EF044B008B914D /* RecommendedValidatorListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2F45325EF044B008B914D /* RecommendedValidatorListViewModel.swift */; }; 84D331AF2519E8080078D044 /* TriangularedView+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D331AE2519E8080078D044 /* TriangularedView+Style.swift */; }; 84D33996262250B800130A89 /* String+Substrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D33995262250B800130A89 /* String+Substrate.swift */; }; - 84D3BD61294C37A700BC8190 /* EthereumTransactionReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D3BD60294C37A700BC8190 /* EthereumTransactionReceipt.swift */; }; 84D6D7F627A7D3060094FC33 /* AssetListTotalBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6D7F527A7D3060094FC33 /* AssetListTotalBalanceCell.swift */; }; 84D6D7F827A7DE120094FC33 /* AssetListAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6D7F727A7DE120094FC33 /* AssetListAccountCell.swift */; }; 84D6D7FA27A7EC810094FC33 /* AssetListAssetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6D7F927A7EC810094FC33 /* AssetListAssetCell.swift */; }; @@ -2373,6 +2365,11 @@ 84E8BA0929FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0829FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift */; }; 84E8BA0B29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0A29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift */; }; 84E8BA0D29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA0C29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift */; }; + 84E8BA1A29FFB38600FD9F40 /* EthereumBlockObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1129FFB38600FD9F40 /* EthereumBlockObject.swift */; }; + 84E8BA1C29FFB38600FD9F40 /* EthereumTransactionReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1329FFB38600FD9F40 /* EthereumTransactionReceipt.swift */; }; + 84E8BA1D29FFB38600FD9F40 /* EthereumResultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1429FFB38600FD9F40 /* EthereumResultParser.swift */; }; + 84E8BA1F29FFB38600FD9F40 /* EthereumOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1629FFB38600FD9F40 /* EthereumOperationFactory.swift */; }; + 84E8BA2229FFB38600FD9F40 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1929FFB38600FD9F40 /* EthereumTransaction.swift */; }; 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E90BA028D0B51000529633 /* CheckboxControlView.swift */; }; 84E9A05028F000AB00551DC4 /* ReferendumMetadataLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */; }; 84EA0B2A25E579DF00AFB0DC /* AssetBalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */; }; @@ -2411,7 +2408,6 @@ 84EDF66D29C4F41C002173E6 /* EvmBalanceMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDF66C29C4F41C002173E6 /* EvmBalanceMessage.swift */; }; 84EDF66F29C4F566002173E6 /* EvmNativeBalanceUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDF66E29C4F566002173E6 /* EvmNativeBalanceUpdateService.swift */; }; 84EDF67329C66015002173E6 /* EvmNativeTransactionHistoryUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDF67229C66015002173E6 /* EvmNativeTransactionHistoryUpdater.swift */; }; - 84EDF67529C662AC002173E6 /* EthereumBlockObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDF67429C662AC002173E6 /* EthereumBlockObject.swift */; }; 84EDF67729C7AC29002173E6 /* HexCodingWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EDF67629C7AC29002173E6 /* HexCodingWrapper.swift */; }; 84EE2FA52891205500A98816 /* WalletManageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE2FA42891205500A98816 /* WalletManageViewController.swift */; }; 84EE2FA72891207500A98816 /* WalletManageViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE2FA62891207500A98816 /* WalletManageViewLayout.swift */; }; @@ -4740,11 +4736,6 @@ 846E501427799B3E0049B659 /* DAppAuthViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAuthViewModelFactory.swift; sourceTree = ""; }; 846E5017277AD3D50049B659 /* DAppList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppList.swift; sourceTree = ""; }; 846F757D27B53F5100536547 /* DAppMetamaskSigningState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppMetamaskSigningState.swift; sourceTree = ""; }; - 846F758027B54D7A00536547 /* EthereumOperationFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumOperationFactory.swift; sourceTree = ""; }; - 846F758127B54D7A00536547 /* EthereumOperationFactor+Protocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EthereumOperationFactor+Protocol.swift"; sourceTree = ""; }; - 846F758427B54F8300536547 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; - 846F758627B5550700536547 /* EthereumRpcRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumRpcRequest.swift; sourceTree = ""; }; - 846F758827B5584100536547 /* EthereumRpcResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumRpcResponse.swift; sourceTree = ""; }; 846FB02B28C74F9700CA5444 /* ParaStkYieldBoostState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParaStkYieldBoostState.swift; sourceTree = ""; }; 846FB02D28C7524200CA5444 /* StakingParachainInteractor+Provide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StakingParachainInteractor+Provide.swift"; sourceTree = ""; }; 847012652982AE5700F29C87 /* StackTableView+Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackTableView+Cell.swift"; sourceTree = ""; }; @@ -5312,7 +5303,6 @@ 84A70DA029CCEDC800C648AD /* GovV2SubsquareOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovV2SubsquareOperationFactory.swift; sourceTree = ""; }; 84A70DA229CCEDE800C648AD /* BaseSubsquareOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseSubsquareOperationFactory.swift; sourceTree = ""; }; 84A7AC7F29465BF9001A39CF /* TokenAddErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenAddErrorPresentable.swift; sourceTree = ""; }; - 84A7AC812946FC31001A39CF /* EthereumResultParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumResultParser.swift; sourceTree = ""; }; 84A8FD8D265FDA76002ADB58 /* CrowdloanContributionConfirmData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanContributionConfirmData.swift; sourceTree = ""; }; 84A90488288EA0E500DFC8E2 /* NoKeysCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoKeysCommand.swift; sourceTree = ""; }; 84A9ECC0291292900094C763 /* GovernanceUnlockConfirmInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceUnlockConfirmInteractorError.swift; sourceTree = ""; }; @@ -5623,7 +5613,6 @@ 84CEF28B290509D600BA25BB /* GovernanceVoteValidatingParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceVoteValidatingParams.swift; sourceTree = ""; }; 84CEF28D29050A3300BA25BB /* DataValidationRunner+GovVote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataValidationRunner+GovVote.swift"; sourceTree = ""; }; 84CF00C227F6C1E4004DB322 /* CustomAssetMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAssetMapper.swift; sourceTree = ""; }; - 84CFBC6428756CCB00E93EDA /* EthereumError+Presentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EthereumError+Presentable.swift"; sourceTree = ""; }; 84CFE440292B8CDA00CDDD7C /* EvmOnChainTransferSetupInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvmOnChainTransferSetupInteractor.swift; sourceTree = ""; }; 84CFE442292B8F3E00CDDD7C /* TransferEvmOnChainConfirmInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferEvmOnChainConfirmInteractor.swift; sourceTree = ""; }; 84CFE447292B9CB100CDDD7C /* OnChainTransferBaseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainTransferBaseInteractor.swift; sourceTree = ""; }; @@ -5679,7 +5668,6 @@ 84D2F45325EF044B008B914D /* RecommendedValidatorListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewModel.swift; sourceTree = ""; }; 84D331AE2519E8080078D044 /* TriangularedView+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TriangularedView+Style.swift"; sourceTree = ""; }; 84D33995262250B800130A89 /* String+Substrate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Substrate.swift"; sourceTree = ""; }; - 84D3BD60294C37A700BC8190 /* EthereumTransactionReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransactionReceipt.swift; sourceTree = ""; }; 84D6D7F527A7D3060094FC33 /* AssetListTotalBalanceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListTotalBalanceCell.swift; sourceTree = ""; }; 84D6D7F727A7DE120094FC33 /* AssetListAccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListAccountCell.swift; sourceTree = ""; }; 84D6D7F927A7EC810094FC33 /* AssetListAssetCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListAssetCell.swift; sourceTree = ""; }; @@ -5824,6 +5812,11 @@ 84E8BA0829FF6ABA00FD9F40 /* ExtrinsicBuilderOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtrinsicBuilderOperationFactory.swift; sourceTree = ""; }; 84E8BA0A29FF760400FD9F40 /* DAppExtrinsicBuilderOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppExtrinsicBuilderOperationFactory.swift; sourceTree = ""; }; 84E8BA0C29FF898600FD9F40 /* BaseExtrinsicOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseExtrinsicOperationFactory.swift; sourceTree = ""; }; + 84E8BA1129FFB38600FD9F40 /* EthereumBlockObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumBlockObject.swift; sourceTree = ""; }; + 84E8BA1329FFB38600FD9F40 /* EthereumTransactionReceipt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumTransactionReceipt.swift; sourceTree = ""; }; + 84E8BA1429FFB38600FD9F40 /* EthereumResultParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumResultParser.swift; sourceTree = ""; }; + 84E8BA1629FFB38600FD9F40 /* EthereumOperationFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumOperationFactory.swift; sourceTree = ""; }; + 84E8BA1929FFB38600FD9F40 /* EthereumTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; 84E90BA028D0B51000529633 /* CheckboxControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxControlView.swift; sourceTree = ""; }; 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataLocal.swift; sourceTree = ""; }; 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetBalanceViewModel.swift; sourceTree = ""; }; @@ -5863,7 +5856,6 @@ 84EDF66C29C4F41C002173E6 /* EvmBalanceMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvmBalanceMessage.swift; sourceTree = ""; }; 84EDF66E29C4F566002173E6 /* EvmNativeBalanceUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvmNativeBalanceUpdateService.swift; sourceTree = ""; }; 84EDF67229C66015002173E6 /* EvmNativeTransactionHistoryUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvmNativeTransactionHistoryUpdater.swift; sourceTree = ""; }; - 84EDF67429C662AC002173E6 /* EthereumBlockObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumBlockObject.swift; sourceTree = ""; }; 84EDF67629C7AC29002173E6 /* HexCodingWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexCodingWrapper.swift; sourceTree = ""; }; 84EE2FA42891205500A98816 /* WalletManageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletManageViewController.swift; sourceTree = ""; }; 84EE2FA62891207500A98816 /* WalletManageViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletManageViewLayout.swift; sourceTree = ""; }; @@ -10008,22 +10000,6 @@ path = PolkadotExtensionProtocol; sourceTree = ""; }; - 846F757F27B54D7A00536547 /* Ethereum */ = { - isa = PBXGroup; - children = ( - 846F758027B54D7A00536547 /* EthereumOperationFactory.swift */, - 846F758127B54D7A00536547 /* EthereumOperationFactor+Protocol.swift */, - 846F758427B54F8300536547 /* EthereumTransaction.swift */, - 846F758627B5550700536547 /* EthereumRpcRequest.swift */, - 846F758827B5584100536547 /* EthereumRpcResponse.swift */, - 84CFBC6428756CCB00E93EDA /* EthereumError+Presentable.swift */, - 84A7AC812946FC31001A39CF /* EthereumResultParser.swift */, - 84D3BD60294C37A700BC8190 /* EthereumTransactionReceipt.swift */, - 84EDF67429C662AC002173E6 /* EthereumBlockObject.swift */, - ); - path = Ethereum; - sourceTree = ""; - }; 847012642982ADCF00F29C87 /* StackTable */ = { isa = PBXGroup; children = ( @@ -11441,12 +11417,12 @@ 8490150124AB6BEA008F705E /* Network */ = { isa = PBXGroup; children = ( + 84E8BA1029FFB38600FD9F40 /* Ethereum */, 84953F642934B2C90033F47D /* Etherscan */, 84C9CF39291AE30F002BF328 /* GovMetadataOperationFactory */, 843E9B3027C8B1D4009C143A /* Files */, 8499FE6D27BE1ABF00712589 /* BaseOperationFactory */, 8499FE6427BD15AC00712589 /* DistributedStorage */, - 846F757F27B54D7A00536547 /* Ethereum */, AECF10EE26E9EF4E005B54A0 /* Coingecko */, 849AFEBA26DCCD4000B65924 /* Subquery */, 849AFEB926DCCCE000B65924 /* Misc */, @@ -13851,6 +13827,18 @@ path = RMRK; sourceTree = ""; }; + 84E8BA1029FFB38600FD9F40 /* Ethereum */ = { + isa = PBXGroup; + children = ( + 84E8BA1129FFB38600FD9F40 /* EthereumBlockObject.swift */, + 84E8BA1329FFB38600FD9F40 /* EthereumTransactionReceipt.swift */, + 84E8BA1429FFB38600FD9F40 /* EthereumResultParser.swift */, + 84E8BA1629FFB38600FD9F40 /* EthereumOperationFactory.swift */, + 84E8BA1929FFB38600FD9F40 /* EthereumTransaction.swift */, + ); + path = Ethereum; + sourceTree = ""; + }; 84EBC54424F660A700459D15 /* EventCenter */ = { isa = PBXGroup; children = ( @@ -17090,6 +17078,7 @@ 84E9A05028F000AB00551DC4 /* ReferendumMetadataLocal.swift in Sources */, 84821E84275F93C700ADC8D2 /* TitleMultiValueView+Style.swift in Sources */, 84DA03D12758AA6800E8B326 /* BaseAccountImportWireframe.swift in Sources */, + 84E8BA1F29FFB38600FD9F40 /* EthereumOperationFactory.swift in Sources */, F4C201822728678400B0F3D0 /* ExtrinsicStatus.swift in Sources */, 842BDB1E278C1CB100AB4B5A /* DAppBrowserStateMachine.swift in Sources */, 88F33F1529CC1F92006125D5 /* Slip44CoinList.swift in Sources */, @@ -17161,6 +17150,7 @@ 8466781627EC9B90007935D3 /* PersistExtrinsicFactory.swift in Sources */, 8490143024A935FE008F705E /* AlertPresentable.swift in Sources */, F4C086B826D10E3400716AEC /* UIRefreshControl+ProgramaticallyBeginRefresh.swift in Sources */, + 84E8BA1D29FFB38600FD9F40 /* EthereumResultParser.swift in Sources */, 8487584527F096F800495306 /* QRScannerWireframe.swift in Sources */, 84E8AC7127BB949300402635 /* RMRKNftV1.swift in Sources */, 846952A22852A1480083E0B4 /* StakingDurationOperationFactoryProtocol.swift in Sources */, @@ -17450,7 +17440,6 @@ 841E555D282E72F500C8438F /* ParachainStakingNetworkInfo.swift in Sources */, 844ADE7928CA7FCB00EE29F7 /* ParaStkYieldBoostSetupPresenter+Protocol.swift in Sources */, 841AAC2526F692EF00F0A25E /* AddressConversion.swift in Sources */, - 846F758927B5584100536547 /* EthereumRpcResponse.swift in Sources */, 8490147624A94A37008F705E /* RootInteractor.swift in Sources */, 845B811B28F445E90040CE84 /* Treasury+Calls.swift in Sources */, 846F757E27B53F5100536547 /* DAppMetamaskSigningState.swift in Sources */, @@ -17967,7 +17956,6 @@ 84D8F16324D8194100AF43E9 /* TitleWithSubtitleTableViewCell.swift in Sources */, F462B35C260C86880005AB01 /* ViewHolder.swift in Sources */, 8846F73E29D7561100B8B776 /* Data+base36.swift in Sources */, - 84EDF67529C662AC002173E6 /* EthereumBlockObject.swift in Sources */, 84948C36287DD1C800E6DD3E /* NftListRMRKV2ViewModel.swift in Sources */, 84C515FB26D84F8C000DBA45 /* AccountImportWrapper.swift in Sources */, 849014DE24AA8F60008F705E /* MainTabBarInteractor.swift in Sources */, @@ -18140,7 +18128,6 @@ 84FEADF228783F55001DFC26 /* BaseParaStakingRewardCalculatoService.swift in Sources */, 848CC93D28D9F6D8009EB4B0 /* OnChainDispatchTime.swift in Sources */, 841E5534282CD9A900C8438F /* StakingType.swift in Sources */, - 84CFBC6528756CCB00E93EDA /* EthereumError+Presentable.swift in Sources */, 847C966325536455002D288F /* ExportRestoreJsonViewFactory.swift in Sources */, F436BB9E2726FBF7004B1794 /* Coordinator.swift in Sources */, 848F5FE1298911B80058CD74 /* SubqueryDelegationsOperationFactory.swift in Sources */, @@ -18265,6 +18252,7 @@ 84216FDE282B979900479375 /* Decimal+Conversion.swift in Sources */, 887404B229826FB100EE270A /* InAppUpdatesServiceFactory.swift in Sources */, 841E2E4E2738159400F250C1 /* RemoteSubscriptionHandlingFactory.swift in Sources */, + 84E8BA2229FFB38600FD9F40 /* EthereumTransaction.swift in Sources */, 846A2601267C768500429A7F /* CrowdloanContributionMapper.swift in Sources */, 84CFF1EA26526FBC00DB7CF7 /* StakingBondMoreConfirmationViewFactory.swift in Sources */, 8466BB472640152A00E065A8 /* StakingUnbondConfirmViewModelFactory.swift in Sources */, @@ -18490,7 +18478,6 @@ 8451720D298C473500489EF1 /* GovernanceSelectableTrackView.swift in Sources */, 84CFF1EE26526FBC00DB7CF7 /* StakingBondMoreConfirmationProtocols.swift in Sources */, 8499FE7B27BE58A000712589 /* IdentityMapper.swift in Sources */, - 846F758227B54D7A00536547 /* EthereumOperationFactory.swift in Sources */, 845B89262959627A00EE25B0 /* SecurityLayerWireframe.swift in Sources */, 8489A6D227FD5FB80040C066 /* StackActionCell.swift in Sources */, F429324F26280F6B00752C2C /* StakingRewardDetailsViewModel.swift in Sources */, @@ -18520,7 +18507,6 @@ 06590486EED4050BADDD32C5 /* AccountManagementPresenter.swift in Sources */, 846A68292746234100D1A47A /* ChainConnection.swift in Sources */, 849A16452995229F00E235FE /* GovernanceDelegateConfirmPresenter+Updates.swift in Sources */, - 846F758327B54D7A00536547 /* EthereumOperationFactor+Protocol.swift in Sources */, 88B1C33F29B9C86D00DCA101 /* BagListCalls.swift in Sources */, 8487584127F0842D00495306 /* AddressQRMatcher.swift in Sources */, 84906FEF28B2C7090049B57D /* LedgerErrorPresentable.swift in Sources */, @@ -18577,7 +18563,6 @@ AE39839C272BFE2B00BC8A85 /* ImportChainAccount+AccountImportWireframe.swift in Sources */, 84592F4F298716E80001BB56 /* GovernanceOffchainVoting.swift in Sources */, 8436E94626C85405003D4EA7 /* RuntimeSnapshot.swift in Sources */, - 846F758527B54F8300536547 /* EthereumTransaction.swift in Sources */, 84216FDC2827E0E100479375 /* BaseParaStakingRewardCalculatoService+Fetch.swift in Sources */, 84BFE8AC28C38C6B00140F1F /* RemoteStorageRequest.swift in Sources */, 847C965425536199002D288F /* ExportRestoreJsonProtocols.swift in Sources */, @@ -18943,7 +18928,6 @@ 8473F4B4282BD5A1007CC55A /* StakingRelaychainInteractor.swift in Sources */, 845B821B26EF80BC00D25C72 /* MetaAccountModel.swift in Sources */, 841E5536282CDB9E00C8438F /* StakingMainPresenterFactory+Relaychain.swift in Sources */, - 84D3BD61294C37A700BC8190 /* EthereumTransactionReceipt.swift in Sources */, 8499FEC827BF73F400712589 /* StorageKeyFactory+Size.swift in Sources */, 847A25CA28D85204006AC9F5 /* ReferendumInfo.swift in Sources */, 5869563D0EA593FBD02C169C /* StakingPayoutConfirmationProtocols.swift in Sources */, @@ -19099,7 +19083,6 @@ 0B2B9C6E2BA2E924D6A54F4B /* CrowdloanListInteractor.swift in Sources */, 849C066F2765140B00394C82 /* AnyCancellableCleaning.swift in Sources */, 8489A6D627FDA50D0040C066 /* AccountLocalStorageSubscriber.swift in Sources */, - 846F758727B5550700536547 /* EthereumRpcRequest.swift in Sources */, 8472C3A1298BF0E100043061 /* GovernanceSelectTrackViewModel.swift in Sources */, 7CBE9FFAF8394786CA131D4D /* CustomValidatorListProtocols.swift in Sources */, 84D9C8EF28AD97E7007FB23B /* SupportedLedgerApps.swift in Sources */, @@ -19203,6 +19186,7 @@ 93434E8E407A6C63D8862A21 /* AssetSelectionProtocols.swift in Sources */, 84E25BEC27E87D5400290BF1 /* TransferDataValidatorFactory.swift in Sources */, 8499FE7127BE214A00712589 /* StorageKeyDecodingOperation.swift in Sources */, + 84E8BA1C29FFB38600FD9F40 /* EthereumTransactionReceipt.swift in Sources */, 84CEF288290462C300BA25BB /* GovernanceValidatorFactory.swift in Sources */, CDB78A5A733E4A4F1A2C48C8 /* AssetSelectionWireframe.swift in Sources */, 2FCB062A2D873BD72B795DB3 /* AssetSelectionPresenter.swift in Sources */, @@ -19253,6 +19237,7 @@ F0675F495766D07473B065F7 /* CrowdloanYourContributionsInteractor.swift in Sources */, 845B080D2918D4F8005785D3 /* Democracy+Call.swift in Sources */, 8425D0EE28FE9BF1003B782A /* ReferendumVoteAction.swift in Sources */, + 84E8BA1A29FFB38600FD9F40 /* EthereumBlockObject.swift in Sources */, 6857DAF09C8D7D5F9C5A5000 /* CrowdloanYourContributionsViewController.swift in Sources */, 487A912B697604FE3367FAEC /* CrowdloanYourContributionsViewLayout.swift in Sources */, 3F7F10D0E1BDE09CBE64BD2D /* CrowdloanYourContributionsViewFactory.swift in Sources */, @@ -19564,7 +19549,6 @@ 5188FF070CD05F92C93A5055 /* CreateWatchOnlyProtocols.swift in Sources */, 97BF7157FE13F723BF4D5713 /* CreateWatchOnlyWireframe.swift in Sources */, 84770F25291F72D700852A33 /* ReferendumVotingInitData.swift in Sources */, - 84A7AC822946FC31001A39CF /* EthereumResultParser.swift in Sources */, 84E0C51E29CA40DA000B65C8 /* OperationContractCallModel.swift in Sources */, 846A835D28B8D09600D92892 /* LedgerMessageSheetViewFactory.swift in Sources */, 60FFEE5B386E82D70333BE80 /* CreateWatchOnlyPresenter.swift in Sources */, diff --git a/novawallet/Common/Network/Ethereum/EthereumError+Presentable.swift b/novawallet/Common/Network/Ethereum/EthereumError+Presentable.swift deleted file mode 100644 index 1a3af4418d..0000000000 --- a/novawallet/Common/Network/Ethereum/EthereumError+Presentable.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -extension EthereumRpcError: ErrorContentConvertible { - func toErrorContent(for locale: Locale?) -> ErrorContent { - let title = R.string.localizable.operationErrorTitle(preferredLanguages: locale?.rLanguages) - let details = "\(message) (code \(code))" - - return ErrorContent(title: title, message: details) - } -} diff --git a/novawallet/Common/Network/Ethereum/EthereumOperationFactor+Protocol.swift b/novawallet/Common/Network/Ethereum/EthereumOperationFactor+Protocol.swift deleted file mode 100644 index 8f164544c1..0000000000 --- a/novawallet/Common/Network/Ethereum/EthereumOperationFactor+Protocol.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Foundation -import RobinHood -import BigInt -import SubstrateSdk - -extension EthereumOperationFactory: EthereumOperationFactoryProtocol { - func createBlockOperation(for blockNumber: BigUInt) -> RobinHood.BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let method = EthereumMethod.blockByNumber.rawValue - let blockNumberString = blockNumber.toHexString() - - let params = JSON.arrayValue( - [ - JSON.stringValue(blockNumberString), // block number - JSON.boolValue(true) // should return full transactions - ] - ) - - let jsonRequest = EthereumRpcRequest(method: method, params: params) - - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue( - HttpContentType.json.rawValue, - forHTTPHeaderField: HttpHeaderKey.contentType.rawValue - ) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } - - func createGasLimitOperation(for transaction: EthereumTransaction) -> BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let method = EthereumMethod.estimateGas.rawValue - let jsonRequest = EthereumRpcRequest(method: method, params: [transaction]) - - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue( - HttpContentType.json.rawValue, - forHTTPHeaderField: HttpHeaderKey.contentType.rawValue - ) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } - - func createGasPriceOperation() -> BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let method = EthereumMethod.gasPrice.rawValue - let jsonRequest = EthereumRpcRequest(method: method) - - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue( - HttpContentType.json.rawValue, - forHTTPHeaderField: HttpHeaderKey.contentType.rawValue - ) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } - - func createTransactionReceiptOperation(for transactionHash: String) -> BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let parameters = [transactionHash] - - let method = EthereumMethod.transactionReceipt.rawValue - let jsonRequest = EthereumRpcRequest(method: method, params: parameters) - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue(HttpContentType.json.rawValue, forHTTPHeaderField: HttpHeaderKey.contentType.rawValue) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } - - func createTransactionsCountOperation( - for accountAddress: Data, - block: EthereumBlock - ) -> BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let parameters = [accountAddress.toHex(includePrefix: true), block.rawValue] - - let method = EthereumMethod.transactionCount.rawValue - let jsonRequest = EthereumRpcRequest(method: method, params: parameters) - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue(HttpContentType.json.rawValue, forHTTPHeaderField: HttpHeaderKey.contentType.rawValue) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } - - func createSendTransactionOperation( - for transactionDataClosure: @escaping () throws -> Data - ) -> BaseOperation { - let url = node - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.post.rawValue - - let method = EthereumMethod.sendRawTransaction.rawValue - let param = try transactionDataClosure().toHex(includePrefix: true) - let jsonRequest = EthereumRpcRequest(method: method, params: [param]) - request.httpBody = try JSONEncoder().encode(jsonRequest) - request.setValue( - HttpContentType.json.rawValue, - forHTTPHeaderField: HttpHeaderKey.contentType.rawValue - ) - - return request - } - - let resultFactory: AnyNetworkResultFactory = createResultFactory() - - return NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - } -} diff --git a/novawallet/Common/Network/Ethereum/EthereumOperationFactory.swift b/novawallet/Common/Network/Ethereum/EthereumOperationFactory.swift index 661c92b7e2..a7cef3c7fd 100644 --- a/novawallet/Common/Network/Ethereum/EthereumOperationFactory.swift +++ b/novawallet/Common/Network/Ethereum/EthereumOperationFactory.swift @@ -36,29 +36,3 @@ enum EthereumMethod: String { case transactionReceipt = "eth_getTransactionReceipt" case blockByNumber = "eth_getBlockByNumber" } - -final class EthereumOperationFactory { - static let errorDomain = "EthereumDomain" - - let node: URL - - init(node: URL) { - self.node = node - } - - func createResultFactory() -> AnyNetworkResultFactory { - AnyNetworkResultFactory { data in - let response = try JSONDecoder().decode(EthereumRpcResponse.self, from: data) - - if let result = response.result { - return result - } - - if let error = response.error { - throw error - } - - throw BaseOperationError.unexpectedDependentResult - } - } -} diff --git a/novawallet/Common/Network/Ethereum/EthereumRpcRequest.swift b/novawallet/Common/Network/Ethereum/EthereumRpcRequest.swift deleted file mode 100644 index 63472f686c..0000000000 --- a/novawallet/Common/Network/Ethereum/EthereumRpcRequest.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -struct EthereumRpcRequest: Codable { - let jsonrpc: String - let method: String - let params: P - let id: UInt16 - - init(method: String, params: P) { - let requestId = UInt16.random(in: 1 ... UInt16.max) - - self.init(version: "2.0", id: requestId, method: method, params: params) - } - - init(version: String, id: UInt16, method: String, params: P) { - jsonrpc = version - self.method = method - self.params = params - self.id = id - } -} - -extension EthereumRpcRequest where P == [String] { - init(method: String) { - let requestId = UInt16.random(in: 1 ... UInt16.max) - - self.init(version: "2.0", id: requestId, method: method, params: []) - } -} diff --git a/novawallet/Common/Network/Ethereum/EthereumRpcResponse.swift b/novawallet/Common/Network/Ethereum/EthereumRpcResponse.swift deleted file mode 100644 index e3b0cb4ae8..0000000000 --- a/novawallet/Common/Network/Ethereum/EthereumRpcResponse.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -struct EthereumRpcError: Codable, Error { - let code: Int - let message: String -} - -struct EthereumRpcResponse: Codable { - enum CodingKeys: String, CodingKey { - case identifier = "id" - case jsonrpc - case error - case result - } - - let identifier: UInt16 - let jsonrpc: String - let error: EthereumRpcError? - let result: T? -} From c4a99bed56996721b5c446d4bb712e627fe7c47b Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 1 May 2023 15:33:17 +0500 Subject: [PATCH 17/54] eth sign fix --- Podfile | 2 +- Podfile.lock | 11 +++++-- novawallet.xcodeproj/project.pbxproj | 4 +++ .../DAppOperationConfirmViewFactory.swift | 2 +- .../WalletConnectEthereumTransaction.swift | 19 ++++++++++++ .../Model/WalletConnectSignModelFactory.swift | 30 +++++++++++++++---- 6 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectEthereumTransaction.swift diff --git a/Podfile b/Podfile index bf9c59999b..2a299856e5 100644 --- a/Podfile +++ b/Podfile @@ -24,7 +24,7 @@ abstract_target 'novawalletAll' do pod 'CDMarkdownKit', :git => 'https://github.com/nova-wallet/CDMarkdownKit.git', :tag => '2.5.2' pod 'web3swift', :git => 'https://github.com/web3swift-team/web3swift.git', :tag => '3.0.6' pod 'WalletConnectSwiftV2' - pod 'EthereumSignTypedDataUtil' + pod 'EthereumSignTypedDataUtil', :git => 'https://github.com/ERussel/EthereumSignTypedDataUtil.git', :tag => '0.1.3' target 'novawalletTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index f63e2b6b14..2411730536 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -154,7 +154,7 @@ DEPENDENCIES: - CDMarkdownKit (from `https://github.com/nova-wallet/CDMarkdownKit.git`, tag `2.5.2`) - CommonWallet/Core (from `https://github.com/ERussel/Capital-iOS.git`, tag `1.16.0`) - Cuckoo - - EthereumSignTypedDataUtil + - EthereumSignTypedDataUtil (from `https://github.com/ERussel/EthereumSignTypedDataUtil.git`, tag `0.1.3`) - FireMock - Kingfisher - R.swift @@ -181,7 +181,6 @@ SPEC REPOS: - CocoaLumberjack - CryptoSwift - Cuckoo - - EthereumSignTypedDataUtil - FireMock - IrohaCrypto - keccak.c @@ -211,6 +210,9 @@ EXTERNAL SOURCES: CommonWallet: :git: https://github.com/ERussel/Capital-iOS.git :tag: 1.16.0 + EthereumSignTypedDataUtil: + :git: https://github.com/ERussel/EthereumSignTypedDataUtil.git + :tag: 0.1.3 SoraUI: :git: https://github.com/ERussel/UIkit-iOS.git :tag: 1.11.1 @@ -236,6 +238,9 @@ CHECKOUT OPTIONS: CommonWallet: :git: https://github.com/ERussel/Capital-iOS.git :tag: 1.16.0 + EthereumSignTypedDataUtil: + :git: https://github.com/ERussel/EthereumSignTypedDataUtil.git + :tag: 0.1.3 SoraUI: :git: https://github.com/ERussel/UIkit-iOS.git :tag: 1.11.1 @@ -291,6 +296,6 @@ SPEC CHECKSUMS: web3swift: 944e76579b953a7b7e81dbb351c6dc0ed1defe63 xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 -PODFILE CHECKSUM: 045fa3d2044604370165702e5bf8e46facda88d9 +PODFILE CHECKSUM: b7a3863003b20c725bfd8afc5f904b1bb506e5cf COCOAPODS: 1.11.3 diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index a4902f6bd9..c717e31a76 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -2370,6 +2370,7 @@ 84E8BA1D29FFB38600FD9F40 /* EthereumResultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1429FFB38600FD9F40 /* EthereumResultParser.swift */; }; 84E8BA1F29FFB38600FD9F40 /* EthereumOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1629FFB38600FD9F40 /* EthereumOperationFactory.swift */; }; 84E8BA2229FFB38600FD9F40 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA1929FFB38600FD9F40 /* EthereumTransaction.swift */; }; + 84E8BA2829FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA2729FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift */; }; 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E90BA028D0B51000529633 /* CheckboxControlView.swift */; }; 84E9A05028F000AB00551DC4 /* ReferendumMetadataLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */; }; 84EA0B2A25E579DF00AFB0DC /* AssetBalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */; }; @@ -5817,6 +5818,7 @@ 84E8BA1429FFB38600FD9F40 /* EthereumResultParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumResultParser.swift; sourceTree = ""; }; 84E8BA1629FFB38600FD9F40 /* EthereumOperationFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumOperationFactory.swift; sourceTree = ""; }; 84E8BA1929FFB38600FD9F40 /* EthereumTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; + 84E8BA2729FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectEthereumTransaction.swift; sourceTree = ""; }; 84E90BA028D0B51000529633 /* CheckboxControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxControlView.swift; sourceTree = ""; }; 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataLocal.swift; sourceTree = ""; }; 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetBalanceViewModel.swift; sourceTree = ""; }; @@ -11875,6 +11877,7 @@ 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */, 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */, 8485034F29FBC84300AE6909 /* WalletConnectSignModelFactory.swift */, + 84E8BA2729FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift */, ); path = Model; sourceTree = ""; @@ -19526,6 +19529,7 @@ EDD5551608E7ACDDBBC054C4 /* ParaStkRebondProtocols.swift in Sources */, F85F1BCAD47F0596FBFBA110 /* ParaStkRebondWireframe.swift in Sources */, B61457C5248F3B0E88A7990E /* ParaStkRebondPresenter.swift in Sources */, + 84E8BA2829FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift in Sources */, F6F73F4862779FE0B58A7931 /* ParaStkRebondInteractor.swift in Sources */, 58D5B4F17DA37C241FF96A5F /* ParaStkRebondViewController.swift in Sources */, F5F74CD4110DB94902AA836A /* ParaStkRebondViewLayout.swift in Sources */, diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift index c2caf78951..50555a3bb7 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift @@ -133,7 +133,7 @@ struct DAppOperationConfirmViewFactory { } operationFactory = EvmWebSocketOperationFactory(connection: connection) - chainId = BigUInt(knownChain.addressPrefix).toHexWithPrefix() + chainId = BigUInt(knownChain.addressPrefix).toHexString() case let .right(unknownChain): guard let connection = HTTPEngine( urls: [unknownChain.rpcUrl], diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectEthereumTransaction.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectEthereumTransaction.swift new file mode 100644 index 0000000000..de6793ddc5 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectEthereumTransaction.swift @@ -0,0 +1,19 @@ +import Foundation +import BigInt + +struct WalletConnectEthereumTransaction: Codable { + let from: String + + // swiftlint:disable:next identifier_name + let to: String? + + @OptionHexCodable var gasLimit: BigUInt? + + @OptionHexCodable var gasPrice: BigUInt? + + @OptionHexCodable var value: BigUInt? + + @OptionHexCodable var nonce: BigUInt? + + let data: String? +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift index 230b45bfae..543176a9f0 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -1,6 +1,7 @@ import Foundation import WalletConnectSwiftV2 import SubstrateSdk +import EthereumSignTypedDataUtil enum WalletConnectSignModelFactoryError: Error { case missingAccount(chainId: ChainModel.Id) @@ -104,14 +105,27 @@ enum WalletConnectSignModelFactory { private static func createEthereumTransaction(for params: AnyCodable) throws -> JSON { let json = try params.get(JSON.self) - guard let transaction = json.arrayValue?.first else { + guard + let wcTransaction = try? json.arrayValue?.first?.map( + to: WalletConnectEthereumTransaction.self + ) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, method: .polkadotSignTransaction ) } - return transaction + let transaction = EthereumTransaction( + from: wcTransaction.from, + to: wcTransaction.to, + gas: wcTransaction.gasLimit?.toHexWithPrefix(), + gasPrice: wcTransaction.gasPrice?.toHexWithPrefix(), + value: wcTransaction.value?.toHexWithPrefix(), + data: wcTransaction.data, + nonce: wcTransaction.nonce?.toHexWithPrefix() + ) + + return try transaction.toScaleCompatibleJSON() } private static func parseAndValidateEthereumParams( @@ -198,10 +212,14 @@ enum WalletConnectSignModelFactory { return JSON.stringValue(typedDataString) } - throw WalletConnectSignModelFactoryError.invalidParams( - params: json, - method: .polkadotSignTransaction - ) + guard let data = typedDataString.data(using: .utf8) else { + throw CommonError.dataCorruption + } + + let typeData = try JSONDecoder().decode(EIP712TypedData.self, from: data) + let hash = try typeData.signableHash(version: .v4) + + return JSON.stringValue(hash.toHex(includePrefix: true)) } } From 31d041f6688323a013b314be755125d1a09bf968 Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 2 May 2023 09:41:19 +0500 Subject: [PATCH 18/54] fix transaction composing --- .../Crypto/EthereumSerializationFactory.swift | 22 +++++++++++++++++++ .../DAppEthereumConfirmInteractor.swift | 19 ++++++++++++---- .../Model/WalletConnectSignModelFactory.swift | 16 ++++++++++++++ .../States/WalletConnectStateSigning.swift | 9 ++++++-- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/novawallet/Common/Crypto/EthereumSerializationFactory.swift b/novawallet/Common/Crypto/EthereumSerializationFactory.swift index 0680970aa6..1cad3e5abb 100644 --- a/novawallet/Common/Crypto/EthereumSerializationFactory.swift +++ b/novawallet/Common/Crypto/EthereumSerializationFactory.swift @@ -9,6 +9,11 @@ protocol EthereumSerializationFactoryProtocol { chainId: String, signature: EthereumSignature? ) throws -> Data + + func serializeSignatureWithReplayProtection( + for signature: EthereumSignature, + chainId: String + ) throws -> Data } enum EthereumSerializationFactoryError: Error { @@ -120,4 +125,21 @@ extension EthereumSerializationFactory: EthereumSerializationFactoryProtocol { return serializedData } + + func serializeSignatureWithReplayProtection( + for signature: EthereumSignature, + chainId: String + ) throws -> Data { + guard let chainId = BigUInt.fromHexString(chainId) else { + throw EthereumSerializationFactoryError.invalidChainId(value: chainId) + } + + let signatureFields = composeSignaturePart(from: signature, chainId: chainId) + + guard let serializedData = RLP.encode(signatureFields) else { + throw EthereumSerializationFactoryError.rlpFailed + } + + return serializedData + } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift index ba69f2dddc..5c0e6576c3 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppEthereumConfirmInteractor.swift @@ -295,15 +295,25 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { signingOperation.addDependency(signatureDataOperation) signingOperation.addDependency(transactionWrapper.targetOperation) - signingOperation.completionBlock = { [weak self] in + let serializationOperation = createSerializationOperation( + chainId: chainId, + dependingOn: transactionWrapper.targetOperation, + signatureOperation: signingOperation, + serializationFactory: serializationFactory + ) + + serializationOperation.addDependency(transactionWrapper.targetOperation) + serializationOperation.addDependency(signingOperation) + + serializationOperation.completionBlock = { [weak self] in DispatchQueue.main.async { guard let strongSelf = self else { return } do { - let signature = try signingOperation.extractNoCancellableResultData() - let response = DAppOperationResponse(signature: signature) + let transaction = try serializationOperation.extractNoCancellableResultData() + let response = DAppOperationResponse(signature: transaction) let result: Result = .success(response) strongSelf.presenter?.didReceive(responseResult: result, for: strongSelf.request) } catch { @@ -313,7 +323,8 @@ final class DAppEthereumConfirmInteractor: DAppOperationBaseInteractor { } } - let allOperations = transactionWrapper.allOperations + [signatureDataOperation, signingOperation] + let allOperations = transactionWrapper.allOperations + + [signatureDataOperation, signingOperation, serializationOperation] operationQueue.addOperations(allOperations, waitUntilFinished: false) } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift index 543176a9f0..3ffdc7f09f 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -278,4 +278,20 @@ extension WalletConnectSignModelFactory { return .ethereumBytes(chain: .left(chain)) } } + + static func createSigningResponse(for method: WalletConnectMethod, signature: Data) -> AnyCodable { + switch method { + case .polkadotSignTransaction, .polkadotSignMessage: + let identifier = (0 ... UInt32.max).randomElement() ?? 0 + let result = PolkadotExtensionSignerResult( + identifier: UInt(identifier), + signature: signature.toHex(includePrefix: true) + ) + + return AnyCodable(result) + case .ethSignTransaction, .ethSendTransaction, .ethPersonalSign, .ethSignTypeData: + let result = signature.toHex(includePrefix: true) + return AnyCodable(result) + } + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index dc2530b91c..ee46dfbfa4 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -28,8 +28,13 @@ extension WalletConnectStateSigning: WalletConnectStateProtocol { let nextState = WalletConnectStateReady(stateMachine: stateMachine) - if let signature = response.signature { - let result = AnyCodable(signature.toHex(includePrefix: true)) + if + let signature = response.signature, + let method = WalletConnectMethod(rawValue: request.method) { + let result = WalletConnectSignModelFactory.createSigningResponse( + for: method, + signature: signature + ) stateMachine.emit( signDecision: .approve(request: request, signature: result), From 123e47b4ba8c5a7f500c573d5801579cc9fc64a2 Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 2 May 2023 11:00:21 +0500 Subject: [PATCH 19/54] fix addresses case --- Podfile | 2 +- Podfile.lock | 35 +++++++++-------- .../Model/WalletConnectSignModelFactory.swift | 38 +++++-------------- .../Transport/WalletConnectTransport.swift | 2 +- novawalletTests/Mocks/CommonMocks.swift | 18 ++++----- .../DAppOperationConfirmTests.swift | 6 ++- 6 files changed, 45 insertions(+), 56 deletions(-) diff --git a/Podfile b/Podfile index 2a299856e5..b60faab6b3 100644 --- a/Podfile +++ b/Podfile @@ -23,7 +23,7 @@ abstract_target 'novawalletAll' do pod 'Starscream', :git => 'https://github.com/ERussel/Starscream.git', :tag => '4.0.5' pod 'CDMarkdownKit', :git => 'https://github.com/nova-wallet/CDMarkdownKit.git', :tag => '2.5.2' pod 'web3swift', :git => 'https://github.com/web3swift-team/web3swift.git', :tag => '3.0.6' - pod 'WalletConnectSwiftV2' + pod 'WalletConnectSwiftV2', :git => 'https://github.com/WalletConnect/WalletConnectSwiftV2.git', :tag => '1.5.14' pod 'EthereumSignTypedDataUtil', :git => 'https://github.com/ERussel/EthereumSignTypedDataUtil.git', :tag => '0.1.3' target 'novawalletTests' do diff --git a/Podfile.lock b/Podfile.lock index 2411730536..5376dd7084 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -122,24 +122,24 @@ PODS: - BigInt (~> 5.0) - SwiftyBeaver (1.9.3) - TweetNacl (1.0.2) - - WalletConnectSwiftV2 (1.5.11): - - WalletConnectSwiftV2/WalletConnect (= 1.5.11) - - WalletConnectSwiftV2/Commons (1.5.11) - - WalletConnectSwiftV2/JSONRPC (1.5.11): + - WalletConnectSwiftV2 (1.5.14): + - WalletConnectSwiftV2/WalletConnect (= 1.5.14) + - WalletConnectSwiftV2/Commons (1.5.14) + - WalletConnectSwiftV2/JSONRPC (1.5.14): - WalletConnectSwiftV2/Commons - - WalletConnectSwiftV2/WalletConnect (1.5.11): + - WalletConnectSwiftV2/WalletConnect (1.5.14): - WalletConnectSwiftV2/WalletConnectPairing - - WalletConnectSwiftV2/WalletConnectJWT (1.5.11): + - WalletConnectSwiftV2/WalletConnectJWT (1.5.14): - WalletConnectSwiftV2/WalletConnectKMS - - WalletConnectSwiftV2/WalletConnectKMS (1.5.11): + - WalletConnectSwiftV2/WalletConnectKMS (1.5.14): - WalletConnectSwiftV2/WalletConnectUtils - - WalletConnectSwiftV2/WalletConnectNetworking (1.5.11): + - WalletConnectSwiftV2/WalletConnectNetworking (1.5.14): - WalletConnectSwiftV2/WalletConnectRelay - - WalletConnectSwiftV2/WalletConnectPairing (1.5.11): + - WalletConnectSwiftV2/WalletConnectPairing (1.5.14): - WalletConnectSwiftV2/WalletConnectNetworking - - WalletConnectSwiftV2/WalletConnectRelay (1.5.11): + - WalletConnectSwiftV2/WalletConnectRelay (1.5.14): - WalletConnectSwiftV2/WalletConnectJWT - - WalletConnectSwiftV2/WalletConnectUtils (1.5.11): + - WalletConnectSwiftV2/WalletConnectUtils (1.5.14): - WalletConnectSwiftV2/JSONRPC - Web3Core (3.0.6): - BigInt (~> 5.2.0) @@ -172,7 +172,7 @@ DEPENDENCIES: - SwiftLint - SwiftRLP (from `https://github.com/ERussel/SwiftRLP.git`) - SwiftyBeaver - - WalletConnectSwiftV2 + - WalletConnectSwiftV2 (from `https://github.com/WalletConnect/WalletConnectSwiftV2.git`, tag `1.5.14`) - web3swift (from `https://github.com/web3swift-team/web3swift.git`, tag `3.0.6`) SPEC REPOS: @@ -199,7 +199,6 @@ SPEC REPOS: - SwiftLint - SwiftyBeaver - TweetNacl - - WalletConnectSwiftV2 - Web3Core - xxHash-Swift @@ -227,6 +226,9 @@ EXTERNAL SOURCES: :tag: 3.0.0 SwiftRLP: :git: https://github.com/ERussel/SwiftRLP.git + WalletConnectSwiftV2: + :git: https://github.com/WalletConnect/WalletConnectSwiftV2.git + :tag: 1.5.14 web3swift: :git: https://github.com/web3swift-team/web3swift.git :tag: 3.0.6 @@ -256,6 +258,9 @@ CHECKOUT OPTIONS: SwiftRLP: :commit: 809e68a002d19ee3d8bbeb72577224b7513e7e8e :git: https://github.com/ERussel/SwiftRLP.git + WalletConnectSwiftV2: + :git: https://github.com/WalletConnect/WalletConnectSwiftV2.git + :tag: 1.5.14 web3swift: :git: https://github.com/web3swift-team/web3swift.git :tag: 3.0.6 @@ -291,11 +296,11 @@ SPEC CHECKSUMS: SwiftRLP: f58417bfceecd45394fc619ccad14cf16e4ae6c1 SwiftyBeaver: 2e8acd6fc90c6d0a27055867a290794926d57c02 TweetNacl: 3abf4d1d2082b0114e7a67410e300892448951e6 - WalletConnectSwiftV2: 57aa9c4fef92428ac74d51d3d08fe5001af94827 + WalletConnectSwiftV2: ff5cb17196b583a72c8ab826dd236ab3aa7b2c39 Web3Core: 4a62a109cac056915d2d5023606438c89e229a1e web3swift: 944e76579b953a7b7e81dbb351c6dc0ed1defe63 xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 -PODFILE CHECKSUM: b7a3863003b20c725bfd8afc5f904b1bb506e5cf +PODFILE CHECKSUM: 17dc836b964fd661520c3c70f3d2c1d46620dd66 COCOAPODS: 1.11.3 diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift index 3ffdc7f09f..2f805849d1 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -16,25 +16,26 @@ enum WalletConnectSignModelFactory { chain: ChainModel, params: AnyCodable ) throws -> JSON { - guard let walletAddress = wallet.fetch(for: chain.accountRequest())?.toAddress() else { + guard let walletAccountId = wallet.fetch(for: chain.accountRequest())?.accountId else { throw WalletConnectSignModelFactoryError.missingAccount(chainId: chain.chainId) } let json = try params.get(JSON.self) - guard let requestAddress = json.address?.stringValue else { + guard + let requestAccountId = try? json.address?.stringValue?.toAccountId( + using: chain.chainFormat + ) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, method: .polkadotSignTransaction ) } - // Wallet Connect can include addresses without checksum - - guard walletAddress.lowercased() == requestAddress.lowercased() else { + guard walletAccountId == requestAccountId else { throw WalletConnectSignModelFactoryError.invalidAccount( - expected: walletAddress, - actual: requestAddress + expected: walletAccountId.toHex(includePrefix: true), + actual: requestAccountId.toHex(includePrefix: true) ) } @@ -52,33 +53,14 @@ enum WalletConnectSignModelFactory { params: params ) - guard - let payload = try json.transactionPayload?.map(to: PolkadotExtensionExtrinsic.self), - let address = wallet.fetch(for: chain.accountRequest())?.toAddress() else { + guard let payload = try json.transactionPayload?.map(to: PolkadotExtensionExtrinsic.self) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, method: .polkadotSignTransaction ) } - // Wallet Connect can include address without checksum, we manually add it for consistency - - let modifiedPayload = PolkadotExtensionExtrinsic( - address: address, - blockHash: payload.blockHash, - blockNumber: payload.blockNumber, - era: payload.era, - genesisHash: payload.genesisHash, - method: payload.method, - nonce: payload.nonce, - specVersion: payload.specVersion, - tip: payload.tip, - transactionVersion: payload.transactionVersion, - signedExtensions: payload.signedExtensions, - version: payload.version - ) - - return try modifiedPayload.toScaleCompatibleJSON() + return try payload.toScaleCompatibleJSON() } private static func createPolkadotSignMessage( diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 8c130ec466..e1d1290f19 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -165,7 +165,7 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { logger.debug("New session: \(establishedSession)") - // TODO: Handle session + // TODO: Handle session in ui task } func walletConnect(service _: WalletConnectServiceProtocol, request: Request, session: Session?) { diff --git a/novawalletTests/Mocks/CommonMocks.swift b/novawalletTests/Mocks/CommonMocks.swift index 2166db8759..354e44522f 100644 --- a/novawalletTests/Mocks/CommonMocks.swift +++ b/novawalletTests/Mocks/CommonMocks.swift @@ -1660,9 +1660,9 @@ import RobinHood - func createAuthorizedDAppsRepository(for metaId: String) -> AnyDataProviderRepository { + func createAuthorizedDAppsRepository(for metaId: String?) -> AnyDataProviderRepository { - return cuckoo_manager.call("createAuthorizedDAppsRepository(for: String) -> AnyDataProviderRepository", + return cuckoo_manager.call("createAuthorizedDAppsRepository(for: String?) -> AnyDataProviderRepository", parameters: (metaId), escapingParameters: (metaId), superclassCall: @@ -1712,9 +1712,9 @@ import RobinHood return .init(stub: cuckoo_manager.createStub(for: MockAccountRepositoryFactoryProtocol.self, method: "createFavoriteDAppsRepository() -> AnyDataProviderRepository", parameterMatchers: matchers)) } - func createAuthorizedDAppsRepository(for metaId: M1) -> Cuckoo.ProtocolStubFunction<(String), AnyDataProviderRepository> where M1.MatchedType == String { - let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: metaId) { $0 }] - return .init(stub: cuckoo_manager.createStub(for: MockAccountRepositoryFactoryProtocol.self, method: "createAuthorizedDAppsRepository(for: String) -> AnyDataProviderRepository", parameterMatchers: matchers)) + func createAuthorizedDAppsRepository(for metaId: M1) -> Cuckoo.ProtocolStubFunction<(String?), AnyDataProviderRepository> where M1.OptionalMatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String?)>] = [wrap(matchable: metaId) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockAccountRepositoryFactoryProtocol.self, method: "createAuthorizedDAppsRepository(for: String?) -> AnyDataProviderRepository", parameterMatchers: matchers)) } func createDAppsGlobalSettingsRepository() -> Cuckoo.ProtocolStubFunction<(), AnyDataProviderRepository> { @@ -1757,9 +1757,9 @@ import RobinHood } @discardableResult - func createAuthorizedDAppsRepository(for metaId: M1) -> Cuckoo.__DoNotUse<(String), AnyDataProviderRepository> where M1.MatchedType == String { - let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: metaId) { $0 }] - return cuckoo_manager.verify("createAuthorizedDAppsRepository(for: String) -> AnyDataProviderRepository", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + func createAuthorizedDAppsRepository(for metaId: M1) -> Cuckoo.__DoNotUse<(String?), AnyDataProviderRepository> where M1.OptionalMatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String?)>] = [wrap(matchable: metaId) { $0 }] + return cuckoo_manager.verify("createAuthorizedDAppsRepository(for: String?) -> AnyDataProviderRepository", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @discardableResult @@ -1797,7 +1797,7 @@ import RobinHood - func createAuthorizedDAppsRepository(for metaId: String) -> AnyDataProviderRepository { + func createAuthorizedDAppsRepository(for metaId: String?) -> AnyDataProviderRepository { return DefaultValueRegistry.defaultValue(for: (AnyDataProviderRepository).self) } diff --git a/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift b/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift index 4bf81e5fae..b2a2d9585e 100644 --- a/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift +++ b/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift @@ -75,6 +75,7 @@ class DAppOperationConfirmTests: XCTestCase { transportName: DAppTransports.polkadotExtension, identifier: UUID().uuidString, wallet: wallet, + accountId: accountId, dApp: "Test", dAppIcon: nil, operationData: jsonRequest @@ -128,7 +129,7 @@ class DAppOperationConfirmTests: XCTestCase { interactor: interactor, wireframe: wireframe, delegate: delegate, - viewModelFactory: DAppOperationConfirmViewModelFactory(), + viewModelFactory: DAppOperationConfirmViewModelFactory(chain: .left(chain)), balanceViewModelFactory: balanceViewModelFactory, localizationManager: LocalizationManager.shared ) @@ -223,6 +224,7 @@ class DAppOperationConfirmTests: XCTestCase { transportName: DAppTransports.polkadotExtension, identifier: UUID().uuidString, wallet: wallet, + accountId: accountId, dApp: "Test", dAppIcon: nil, operationData: jsonRequest @@ -245,7 +247,7 @@ class DAppOperationConfirmTests: XCTestCase { interactor: interactor, wireframe: wireframe, delegate: delegate, - viewModelFactory: DAppOperationConfirmViewModelFactory(), + viewModelFactory: DAppOperationConfirmViewModelFactory(chain: .left(chain)), balanceViewModelFactory: balanceViewModelFactory, localizationManager: LocalizationManager.shared ) From d0edb92fc3ee3dc2606b3d0175ae3c8129f077a9 Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 2 May 2023 12:54:48 +0500 Subject: [PATCH 20/54] add support xcm v2 for orml --- novawallet.xcodeproj/project.pbxproj | 48 +++--- .../Xcm/XcmTransferService+Compose.swift | 159 ++++++++++-------- .../Substrate/Calls/Xcm/XcmOrmlTransfer.swift | 22 ++- .../Xcm/XcmBaseMetadataQueryFactory.swift | 32 ++++ .../Xcm/XcmPalletMetadataQueryFactory.swift | 42 ++--- .../CrossChainTransferPresenter.swift | 8 +- ...sferSetupPresenterFactory+CrossChain.swift | 3 +- 7 files changed, 186 insertions(+), 128 deletions(-) create mode 100644 novawallet/Common/Substrate/Types/Xcm/XcmBaseMetadataQueryFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index abd5bda6d0..b84a4ff832 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -2337,6 +2337,7 @@ 84E8AC7127BB949300402635 /* RMRKNftV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7027BB949300402635 /* RMRKNftV1.swift */; }; 84E8AC7527BB975700402635 /* RMRKV1OperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7427BB975700402635 /* RMRKV1OperationFactory.swift */; }; 84E8AC7727BBC8E400402635 /* NFTIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8AC7627BBC8E400402635 /* NFTIntegrationTests.swift */; }; + 84E8BA2A2A00EF4C00FD9F40 /* XcmBaseMetadataQueryFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BA292A00EF4C00FD9F40 /* XcmBaseMetadataQueryFactory.swift */; }; 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E90BA028D0B51000529633 /* CheckboxControlView.swift */; }; 84E9A05028F000AB00551DC4 /* ReferendumMetadataLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */; }; 84EA0B2A25E579DF00AFB0DC /* AssetBalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */; }; @@ -2596,13 +2597,13 @@ 8814774029B07B66001E98A1 /* DelegateGroupActionTitleControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814773F29B07B66001E98A1 /* DelegateGroupActionTitleControl.swift */; }; 8814774229B07E4C001E98A1 /* ResizableImageActionIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814774129B07E4C001E98A1 /* ResizableImageActionIndicator.swift */; }; 8814E858297E35D600A722FF /* GoveranaceDelegatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8814E857297E35D600A722FF /* GoveranaceDelegatePicker.swift */; }; - 881CA22529E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */; }; - 881CA22729E3F20B00159C5B /* ChainModel+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */; }; - 881CA22929E406F700159C5B /* Paths+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */; }; 881BA30529DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */; }; 881BA30729DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30629DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift */; }; 881BA30929DC1B3000EB8C48 /* kilt-addresses.json in Resources */ = {isa = PBXBuildFile; fileRef = 881BA30829DC1B3000EB8C48 /* kilt-addresses.json */; }; 881BA30B29DC3DD400EB8C48 /* Data+baseEncoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881BA30A29DC3DD400EB8C48 /* Data+baseEncoded.swift */; }; + 881CA22529E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */; }; + 881CA22729E3F20B00159C5B /* ChainModel+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */; }; + 881CA22929E406F700159C5B /* Paths+Equilibrium.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */; }; 8824D4222902D92F0022D778 /* ReferendumFullDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D4212902D92F0022D778 /* ReferendumFullDetailsInteractor.swift */; }; 8824D424290324260022D778 /* PrettyPrintedJSONOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D423290324260022D778 /* PrettyPrintedJSONOperationFactory.swift */; }; 8824D42629032B410022D778 /* BlurredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8824D42529032B410022D778 /* BlurredView.swift */; }; @@ -2688,14 +2689,14 @@ 8860F3E2289D4FFD00C0BF86 /* SectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E1289D4FFD00C0BF86 /* SectionProtocol.swift */; }; 8860F3E4289D50BA00C0BF86 /* Array+SectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E3289D50BA00C0BF86 /* Array+SectionProtocol.swift */; }; 8860F3E8289D7CF400C0BF86 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8860F3E7289D7CF400C0BF86 /* Atomic.swift */; }; + 8863C7AC29D499D30068AD54 /* Web3NameService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AB29D499D30068AD54 /* Web3NameService.swift */; }; + 8863C7B029D49CB70068AD54 /* Web3NameViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */; }; 886A277E29E93995003B269F /* EquilibriumAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A277D29E93995003B269F /* EquilibriumAccountInfo.swift */; }; 886A278029E939D9003B269F /* EquilibriumAccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A277F29E939D9003B269F /* EquilibriumAccountData.swift */; }; 886A278229E939E1003B269F /* EquilibriumRemoteBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278129E939E1003B269F /* EquilibriumRemoteBalance.swift */; }; 886A278429E93A5D003B269F /* SignedBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278329E93A5D003B269F /* SignedBalance.swift */; }; 886A278629E93A84003B269F /* EquilibriumReservedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278529E93A84003B269F /* EquilibriumReservedData.swift */; }; 886A278829E93D2A003B269F /* CommonOperationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886A278729E93D2A003B269F /* CommonOperationWrapper.swift */; }; - 8863C7AC29D499D30068AD54 /* Web3NameService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AB29D499D30068AD54 /* Web3NameService.swift */; }; - 8863C7B029D49CB70068AD54 /* Web3NameViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */; }; 886CA9622977E9B300FC255A /* GovernanceDelegateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886CA9602977E9B200FC255A /* GovernanceDelegateTableViewCell.swift */; }; 886CA9632977E9B300FC255A /* GovernanceDelegateBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886CA9612977E9B200FC255A /* GovernanceDelegateBanner.swift */; }; 886E8CF81EF2566D98D9693E /* ExportSeedViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */; }; @@ -5752,6 +5753,7 @@ 84E8AC7027BB949300402635 /* RMRKNftV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMRKNftV1.swift; sourceTree = ""; }; 84E8AC7427BB975700402635 /* RMRKV1OperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMRKV1OperationFactory.swift; sourceTree = ""; }; 84E8AC7627BBC8E400402635 /* NFTIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTIntegrationTests.swift; sourceTree = ""; }; + 84E8BA292A00EF4C00FD9F40 /* XcmBaseMetadataQueryFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcmBaseMetadataQueryFactory.swift; sourceTree = ""; }; 84E90BA028D0B51000529633 /* CheckboxControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxControlView.swift; sourceTree = ""; }; 84E9A04F28F000AB00551DC4 /* ReferendumMetadataLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataLocal.swift; sourceTree = ""; }; 84EA0B2925E579DF00AFB0DC /* AssetBalanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetBalanceViewModel.swift; sourceTree = ""; }; @@ -6014,13 +6016,13 @@ 8814773F29B07B66001E98A1 /* DelegateGroupActionTitleControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateGroupActionTitleControl.swift; sourceTree = ""; }; 8814774129B07E4C001E98A1 /* ResizableImageActionIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizableImageActionIndicator.swift; sourceTree = ""; }; 8814E857297E35D600A722FF /* GoveranaceDelegatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoveranaceDelegatePicker.swift; sourceTree = ""; }; - 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAssetBalanceUpdatingService.swift; sourceTree = ""; }; - 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChainModel+Equilibrium.swift"; sourceTree = ""; }; - 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Paths+Equilibrium.swift"; sourceTree = ""; }; 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifier.swift; sourceTree = ""; }; 881BA30629DBCD6300EB8C48 /* Web3NameIntegrityVerifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameIntegrityVerifierTests.swift; sourceTree = ""; }; 881BA30829DC1B3000EB8C48 /* kilt-addresses.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "kilt-addresses.json"; sourceTree = ""; }; 881BA30A29DC3DD400EB8C48 /* Data+baseEncoded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+baseEncoded.swift"; sourceTree = ""; }; + 881CA22429E3F0D100159C5B /* EquilibriumAssetBalanceUpdatingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAssetBalanceUpdatingService.swift; sourceTree = ""; }; + 881CA22629E3F20B00159C5B /* ChainModel+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChainModel+Equilibrium.swift"; sourceTree = ""; }; + 881CA22829E406F700159C5B /* Paths+Equilibrium.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Paths+Equilibrium.swift"; sourceTree = ""; }; 881ED96729758088000C0457 /* Array+safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+safe.swift"; sourceTree = ""; }; 8821119C96944A0E3526E93A /* StakingRedeemViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRedeemViewFactory.swift; sourceTree = ""; }; 8824D4212902D92F0022D778 /* ReferendumFullDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumFullDetailsInteractor.swift; sourceTree = ""; }; @@ -6107,14 +6109,14 @@ 8860F3E1289D4FFD00C0BF86 /* SectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionProtocol.swift; sourceTree = ""; }; 8860F3E3289D50BA00C0BF86 /* Array+SectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SectionProtocol.swift"; sourceTree = ""; }; 8860F3E7289D7CF400C0BF86 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; + 8863C7AB29D499D30068AD54 /* Web3NameService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameService.swift; sourceTree = ""; }; + 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameViewModelFactory.swift; sourceTree = ""; }; 886A277D29E93995003B269F /* EquilibriumAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumAccountInfo.swift; sourceTree = ""; }; 886A277F29E939D9003B269F /* EquilibriumAccountData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquilibriumAccountData.swift; sourceTree = ""; }; 886A278129E939E1003B269F /* EquilibriumRemoteBalance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquilibriumRemoteBalance.swift; sourceTree = ""; }; 886A278329E93A5D003B269F /* SignedBalance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedBalance.swift; sourceTree = ""; }; 886A278529E93A84003B269F /* EquilibriumReservedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquilibriumReservedData.swift; sourceTree = ""; }; 886A278729E93D2A003B269F /* CommonOperationWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonOperationWrapper.swift; sourceTree = ""; }; - 8863C7AB29D499D30068AD54 /* Web3NameService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameService.swift; sourceTree = ""; }; - 8863C7AF29D49CB70068AD54 /* Web3NameViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NameViewModelFactory.swift; sourceTree = ""; }; 886CA9602977E9B200FC255A /* GovernanceDelegateTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateTableViewCell.swift; sourceTree = ""; }; 886CA9612977E9B200FC255A /* GovernanceDelegateBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateBanner.swift; sourceTree = ""; }; 887248072924F54900B0D2CC /* URL+Matchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Matchable.swift"; sourceTree = ""; }; @@ -14084,6 +14086,7 @@ 84FB9E25285C6C5000B42FC0 /* XcmVersionedMultiasset.swift */, 84EBFCEF285E84C30006327E /* XcmMultiassetFilter.swift */, 844A539429BF54BA00C77111 /* XcmPalletMetadataQueryFactory.swift */, + 84E8BA292A00EF4C00FD9F40 /* XcmBaseMetadataQueryFactory.swift */, ); path = Xcm; sourceTree = ""; @@ -14462,6 +14465,18 @@ path = View; sourceTree = ""; }; + 8863C7AA29D499BC0068AD54 /* Web3Name */ = { + isa = PBXGroup; + children = ( + 8863C7AB29D499D30068AD54 /* Web3NameService.swift */, + 88F33F1429CC1F92006125D5 /* Slip44CoinList.swift */, + 8886020A29D101B000C6344C /* Web3NameServiceError.swift */, + 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */, + 84C1E7C829EE990800D37668 /* Web3NameProvider.swift */, + ); + path = Web3Name; + sourceTree = ""; + }; 886A277C29E9398B003B269F /* Model */ = { isa = PBXGroup; children = ( @@ -14476,18 +14491,6 @@ path = Model; sourceTree = ""; }; - 8863C7AA29D499BC0068AD54 /* Web3Name */ = { - isa = PBXGroup; - children = ( - 8863C7AB29D499D30068AD54 /* Web3NameService.swift */, - 88F33F1429CC1F92006125D5 /* Slip44CoinList.swift */, - 8886020A29D101B000C6344C /* Web3NameServiceError.swift */, - 881BA30429DBCB8100EB8C48 /* Web3NameIntegrityVerifier.swift */, - 84C1E7C829EE990800D37668 /* Web3NameProvider.swift */, - ); - path = Web3Name; - sourceTree = ""; - }; 886CA96429785D9200FC255A /* Model */ = { isa = PBXGroup; children = ( @@ -18234,6 +18237,7 @@ 84EBC55124F660A700459D15 /* EventVisitor.swift in Sources */, 842B2FCF2947352A002829B6 /* UITextField+Attributes.swift in Sources */, 8452585127ABC531004F9082 /* AssetsSettingsViewModel.swift in Sources */, + 84E8BA2A2A00EF4C00FD9F40 /* XcmBaseMetadataQueryFactory.swift in Sources */, 8454C21D2632A78900657DAD /* EventRecord.swift in Sources */, 84AE7AB527D39DCA00495267 /* NetworkViewModel.swift in Sources */, 84C4C2F9255DB9510045B582 /* PinChangeInteractor.swift in Sources */, diff --git a/novawallet/Common/Services/ExtrinsicService/Substrate/Xcm/XcmTransferService+Compose.swift b/novawallet/Common/Services/ExtrinsicService/Substrate/Xcm/XcmTransferService+Compose.swift index 7998157f1f..607fcf195d 100644 --- a/novawallet/Common/Services/ExtrinsicService/Substrate/Xcm/XcmTransferService+Compose.swift +++ b/novawallet/Common/Services/ExtrinsicService/Substrate/Xcm/XcmTransferService+Compose.swift @@ -12,57 +12,27 @@ extension XcmTransferService { ) -> CompoundOperationWrapper { switch type { case .xtokens: - // keep xtokens version to 1 until pallet is updated - do { - let multilocationAsset = try xcmFactory.createMultilocationAsset( - for: .init( - origin: request.origin, - reserve: request.reserve.chain, - destination: request.destination, - amount: request.amount, - xcmTransfers: xcmTransfers - ), - version: .init(multiLocation: .V1, multiAssets: .V1) - ) - - return .createWithResult(multilocationAsset) - } catch { - return .createWithError(error) - } - case .xcmpallet, .teleport: - let multiassetsVersionWrapper = metadataQueryFactory.createLowestMultiassetsVersionWrapper( + let multiassetVersionWrapper = metadataQueryFactory.createLowestMultiassetVersionWrapper( for: runtimeProvider ) - let multilocationVersionWrapper = metadataQueryFactory.createLowestMultilocationVersionWrapper( + return createMultilocationAssetWrapper( + for: multiassetVersionWrapper, + request: request, + xcmTransfers: xcmTransfers, + runtimeProvider: runtimeProvider + ) + case .xcmpallet, .teleport: + let multiassetsVersionWrapper = metadataQueryFactory.createLowestMultiassetsVersionWrapper( for: runtimeProvider ) - let transferFactory = xcmFactory - - let mappingOperation = ClosureOperation { - let multiassetsVersion = try multiassetsVersionWrapper.targetOperation.extractNoCancellableResultData() - let multilocationVersion = try multilocationVersionWrapper.targetOperation - .extractNoCancellableResultData() - - return try transferFactory.createMultilocationAsset( - for: .init( - origin: request.origin, - reserve: request.reserve.chain, - destination: request.destination, - amount: request.amount, - xcmTransfers: xcmTransfers - ), - version: .init(multiLocation: multilocationVersion, multiAssets: multiassetsVersion) - ) - } - - mappingOperation.addDependency(multiassetsVersionWrapper.targetOperation) - mappingOperation.addDependency(multilocationVersionWrapper.targetOperation) - - let dependecies = multiassetsVersionWrapper.allOperations + multilocationVersionWrapper.allOperations - - return .init(targetOperation: mappingOperation, dependencies: dependecies) + return createMultilocationAssetWrapper( + for: multiassetsVersionWrapper, + request: request, + xcmTransfers: xcmTransfers, + runtimeProvider: runtimeProvider + ) case .unknown: return .createWithError(XcmAssetTransfer.TransferTypeError.unknownType) } @@ -122,30 +92,11 @@ extension XcmTransferService { ) -> CompoundOperationWrapper<(ExtrinsicBuilderClosure, CallCodingPath)> { switch xcmTransfer.type { case .xtokens: - let coderFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() - - let mapOperation = ClosureOperation<(ExtrinsicBuilderClosure, CallCodingPath)> { - let module = try moduleResolutionOperation.extractNoCancellableResultData() - let codingFactory = try coderFactoryOperation.extractNoCancellableResultData() - let destinationAsset = try destinationAssetOperation.extractNoCancellableResultData() - - let asset = destinationAsset.asset - let location = destinationAsset.location - - return try Xcm.appendTransferCall( - asset: asset, - destination: location, - weight: maxWeight, - module: module, - codingFactory: codingFactory - ) - } - - mapOperation.addDependency(coderFactoryOperation) - - return CompoundOperationWrapper( - targetOperation: mapOperation, - dependencies: [coderFactoryOperation] + return createOrmlTransferMapping( + dependingOn: moduleResolutionOperation, + destinationAssetOperation: destinationAssetOperation, + maxWeight: maxWeight, + runtimeProvider: runtimeProvider ) case .xcmpallet: return createPalletXcmTransferMapping( @@ -168,6 +119,43 @@ extension XcmTransferService { } } + private func createMultilocationAssetWrapper( + for multiassetVersionWrapper: CompoundOperationWrapper, + request: XcmUnweightedTransferRequest, + xcmTransfers: XcmTransfers, + runtimeProvider: RuntimeProviderProtocol + ) -> CompoundOperationWrapper { + let multilocationVersionWrapper = metadataQueryFactory.createLowestMultilocationVersionWrapper( + for: runtimeProvider + ) + + let transferFactory = xcmFactory + + let mappingOperation = ClosureOperation { + let multiassetVersion = try multiassetVersionWrapper.targetOperation.extractNoCancellableResultData() + let multilocationVersion = try multilocationVersionWrapper.targetOperation + .extractNoCancellableResultData() + + return try transferFactory.createMultilocationAsset( + for: .init( + origin: request.origin, + reserve: request.reserve.chain, + destination: request.destination, + amount: request.amount, + xcmTransfers: xcmTransfers + ), + version: .init(multiLocation: multilocationVersion, multiAssets: multiassetVersion) + ) + } + + mappingOperation.addDependency(multiassetVersionWrapper.targetOperation) + mappingOperation.addDependency(multilocationVersionWrapper.targetOperation) + + let dependecies = multiassetVersionWrapper.allOperations + multilocationVersionWrapper.allOperations + + return .init(targetOperation: mappingOperation, dependencies: dependecies) + } + private func createPalletXcmTransferMapping( dependingOn moduleResolutionOperation: BaseOperation, callPathFactory: @escaping (String) -> CallCodingPath, @@ -213,4 +201,37 @@ extension XcmTransferService { return CompoundOperationWrapper(targetOperation: mapOperation, dependencies: [coderFactoryOperation]) } + + private func createOrmlTransferMapping( + dependingOn moduleResolutionOperation: BaseOperation, + destinationAssetOperation: BaseOperation, + maxWeight: BigUInt, + runtimeProvider: RuntimeProviderProtocol + ) -> CompoundOperationWrapper<(ExtrinsicBuilderClosure, CallCodingPath)> { + let coderFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + + let mapOperation = ClosureOperation<(ExtrinsicBuilderClosure, CallCodingPath)> { + let module = try moduleResolutionOperation.extractNoCancellableResultData() + let codingFactory = try coderFactoryOperation.extractNoCancellableResultData() + let destinationAsset = try destinationAssetOperation.extractNoCancellableResultData() + + let asset = destinationAsset.asset + let location = destinationAsset.location + + return try Xcm.appendOrmlTransferCall( + asset: asset, + destination: location, + weight: maxWeight, + module: module, + codingFactory: codingFactory + ) + } + + mapOperation.addDependency(coderFactoryOperation) + + return CompoundOperationWrapper( + targetOperation: mapOperation, + dependencies: [coderFactoryOperation] + ) + } } diff --git a/novawallet/Common/Substrate/Calls/Xcm/XcmOrmlTransfer.swift b/novawallet/Common/Substrate/Calls/Xcm/XcmOrmlTransfer.swift index fdeec32b43..2ac175b177 100644 --- a/novawallet/Common/Substrate/Calls/Xcm/XcmOrmlTransfer.swift +++ b/novawallet/Common/Substrate/Calls/Xcm/XcmOrmlTransfer.swift @@ -42,7 +42,7 @@ extension Xcm { let destination: VersionedMultilocation // must be set as maximum between reserve and destination - let destinationWeightLimit: Xcm.WeightLimit + let destinationWeightLimit: Xcm.WeightLimit func runtimeCall(for module: String) -> RuntimeCall { RuntimeCall(moduleName: module, callName: Xcm.ormlTransferCallName, args: self) @@ -53,7 +53,7 @@ extension Xcm { } } - static func appendTransferCall( + static func appendOrmlTransferCall( asset: VersionedMultiasset, destination: VersionedMultilocation, weight: BigUInt, @@ -66,10 +66,10 @@ extension Xcm { return ({ $0 }, path) } - let paramName = OrmlTransferCallV1.CodingKeys.destinationWeight.rawValue + let paramNameV1 = OrmlTransferCallV1.CodingKeys.destinationWeight.rawValue // v1 require only uint64 weight and v2 requires weight limit - let isV1 = callType.isArgumentTypeOf(paramName) { argumentType in + let isV1 = callType.isArgumentTypeOf(paramNameV1) { argumentType in codingFactory.isUInt64Type(argumentType) } @@ -78,10 +78,22 @@ extension Xcm { return ({ try $0.adding(call: call.runtimeCall(for: module)) }, path) } else { + let paramNameV2 = OrmlTransferCallV2.CodingKeys.destinationWeightLimit.rawValue + + let optWeightJson = try BlockchainWeightFactory.convertCallVersionedWeightInWeightLimitToJson( + for: .init(path: path, argName: paramNameV2), + codingFactory: codingFactory, + weight: UInt64(weight) + ) + + guard let weightJson = optWeightJson else { + throw XcmTransferServiceError.noArgumentFound(paramNameV2) + } + let call = OrmlTransferCallV2( asset: asset, destination: destination, - destinationWeightLimit: .limited(weight: .init(value: UInt64(weight))) + destinationWeightLimit: .limited(weight: weightJson) ) return ({ try $0.adding(call: call.runtimeCall(for: module)) }, path) diff --git a/novawallet/Common/Substrate/Types/Xcm/XcmBaseMetadataQueryFactory.swift b/novawallet/Common/Substrate/Types/Xcm/XcmBaseMetadataQueryFactory.swift new file mode 100644 index 0000000000..560c81897e --- /dev/null +++ b/novawallet/Common/Substrate/Types/Xcm/XcmBaseMetadataQueryFactory.swift @@ -0,0 +1,32 @@ +import Foundation +import RobinHood +import SubstrateSdk + +class XcmBaseMetadataQueryFactory { + func createXcmTypeVersionWrapper( + for runtimeProvider: RuntimeProviderProtocol, + typeName: String + ) -> CompoundOperationWrapper { + let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + let searchOperation = ClosureOperation { + guard + let node = try codingFactoryOperation.extractNoCancellableResultData().getTypeNode( + for: typeName + ) else { + return nil + } + + guard let versionNode = node as? SiVariantNode else { + return nil + } + + return versionNode.typeMapping + .compactMap { Xcm.Version(rawName: $0.name) } + .min() + } + + searchOperation.addDependency(codingFactoryOperation) + + return CompoundOperationWrapper(targetOperation: searchOperation, dependencies: [codingFactoryOperation]) + } +} diff --git a/novawallet/Common/Substrate/Types/Xcm/XcmPalletMetadataQueryFactory.swift b/novawallet/Common/Substrate/Types/Xcm/XcmPalletMetadataQueryFactory.swift index b0b1d82e3e..7728fdbd2c 100644 --- a/novawallet/Common/Substrate/Types/Xcm/XcmPalletMetadataQueryFactory.swift +++ b/novawallet/Common/Substrate/Types/Xcm/XcmPalletMetadataQueryFactory.swift @@ -11,47 +11,31 @@ protocol XcmPalletMetadataQueryFactoryProtocol { for runtimeProvider: RuntimeProviderProtocol ) -> CompoundOperationWrapper + func createLowestMultiassetVersionWrapper( + for runtimeProvider: RuntimeProviderProtocol + ) -> CompoundOperationWrapper + func createLowestMultilocationVersionWrapper( for runtimeProvider: RuntimeProviderProtocol ) -> CompoundOperationWrapper } -final class XcmPalletMetadataQueryFactory { - func createXcmTypeVersionWrapper( - for runtimeProvider: RuntimeProviderProtocol, - typeName: String +final class XcmPalletMetadataQueryFactory: XcmBaseMetadataQueryFactory, XcmPalletMetadataQueryFactoryProtocol { + func createLowestMultiassetsVersionWrapper( + for runtimeProvider: RuntimeProviderProtocol ) -> CompoundOperationWrapper { - let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() - let searchOperation = ClosureOperation { - guard - let node = try codingFactoryOperation.extractNoCancellableResultData().getTypeNode( - for: typeName - ) else { - return nil - } - - guard let versionNode = node as? SiVariantNode else { - return nil - } - - return versionNode.typeMapping - .compactMap { Xcm.Version(rawName: $0.name) } - .min() - } - - searchOperation.addDependency(codingFactoryOperation) - - return CompoundOperationWrapper(targetOperation: searchOperation, dependencies: [codingFactoryOperation]) + createXcmTypeVersionWrapper( + for: runtimeProvider, + typeName: "xcm.VersionedMultiAssets" + ) } -} -extension XcmPalletMetadataQueryFactory: XcmPalletMetadataQueryFactoryProtocol { - func createLowestMultiassetsVersionWrapper( + func createLowestMultiassetVersionWrapper( for runtimeProvider: RuntimeProviderProtocol ) -> CompoundOperationWrapper { createXcmTypeVersionWrapper( for: runtimeProvider, - typeName: "xcm.VersionedMultiAssets" + typeName: "xcm.VersionedMultiAsset" ) } diff --git a/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift b/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift index aa37159e20..1973fd158d 100644 --- a/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift +++ b/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift @@ -207,7 +207,9 @@ class CrossChainTransferPresenter { switch result { case let .success(fee): originFee = fee - case .failure: + case let .failure(error): + logger?.error("Origin fee error: \(error)") + askOriginFeeRetry() } } @@ -216,7 +218,9 @@ class CrossChainTransferPresenter { switch result { case let .success(fee): crossChainFee = fee - case .failure: + case let .failure(error): + logger?.error("Crosschain fee error: \(error)") + askCrossChainFeeRetry() } } diff --git a/novawallet/Modules/Transfer/TransferSetup/CrossChain/TransferSetupPresenterFactory+CrossChain.swift b/novawallet/Modules/Transfer/TransferSetup/CrossChain/TransferSetupPresenterFactory+CrossChain.swift index 957b6f0103..902ccd4d82 100644 --- a/novawallet/Modules/Transfer/TransferSetup/CrossChain/TransferSetupPresenterFactory+CrossChain.swift +++ b/novawallet/Modules/Transfer/TransferSetup/CrossChain/TransferSetupPresenterFactory+CrossChain.swift @@ -72,7 +72,8 @@ extension TransferSetupPresenterFactory { utilityBalanceViewModelFactory: utilityBalanceViewModelFactory, dataValidatingFactory: dataValidatingFactory, phishingValidatingFactory: phishingValidatingFactory, - localizationManager: LocalizationManager.shared + localizationManager: LocalizationManager.shared, + logger: Logger.shared ) presenter.view = view From e3c6d0c9f2e32db976d02ef2323eb667547845d6 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 3 May 2023 15:07:25 +0500 Subject: [PATCH 21/54] add wallet connect logic to settings --- novawallet.xcodeproj/project.pbxproj | 102 ++++++++++++++---- .../iconWalletConnect.imageset/Contents.json | 12 +++ .../iconWalletConnect.pdf | Bin 0 -> 2072 bytes .../Common/Services/ServiceCoordinator.swift | 13 ++- .../WalletConnect/WalletConnectService.swift | 10 ++ .../DAppInteractionFactory.swift | 13 ++- .../DAppInteractionMediator.swift | 4 +- .../DAppInteractionProtocols.swift | 8 +- .../Model/WalletConnectSession.swift | 10 ++ .../WalletConnectScanPresentable.swift} | 9 +- .../Service/WalletConnectInteractor.swift | 96 +++++++++++++++++ .../Service/WalletConnectPresenter.swift | 13 +++ .../Service/WalletConnectProtocols.swift | 16 +++ .../Service/WalletConnectServiceFactory.swift | 31 ++++++ .../WalletConnectSessionsInteractor.swift | 25 +++++ .../WalletConnectSessionsPresenter.swift} | 18 ++-- .../WalletConnectSessionsProtocols.swift | 15 +++ .../WalletConnectSessionsViewController.swift | 41 +++++++ .../WalletConnectSessionsViewFactory.swift | 39 +++++++ .../WalletConnectSessionsViewLayout.swift | 12 +++ .../WalletConnectSessionsWireframe.swift | 3 + .../Transport/WalletConnectTransport.swift | 10 +- .../WalletConnectInteractor.swift | 61 ----------- .../WalletConnectProtocols.swift | 18 ---- .../WalletConnectViewController.swift | 47 -------- .../WalletConnectViewFactory.swift | 44 -------- .../WalletConnectViewLayout.swift | 21 ---- .../MainTabBar/MainTabBarViewFactory.swift | 12 ++- .../Settings/Model/SettingsParameters.swift | 5 + .../Modules/Settings/SettingsInteractor.swift | 22 ++++ .../Modules/Settings/SettingsPresenter.swift | 22 +++- .../Modules/Settings/SettingsProtocols.swift | 14 +-- .../Settings/SettingsViewFactory.swift | 17 ++- .../Modules/Settings/SettingsWireframe.swift | 11 +- .../Settings/ViewModel/SettingsRow.swift | 2 +- .../ViewModel/SettingsViewModelFactory.swift | 43 +++++++- .../WalletConnectSessionsTests.swift | 16 +++ 37 files changed, 602 insertions(+), 253 deletions(-) create mode 100644 novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/iconWalletConnect.pdf create mode 100644 novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift rename novawallet/Modules/DApp/WalletConnect/{WalletConnectWireframe.swift => Protocols/WalletConnectScanPresentable.swift} (52%) create mode 100644 novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift rename novawallet/Modules/DApp/WalletConnect/{WalletConnectPresenter.swift => Sessions/WalletConnectSessionsPresenter.swift} (50%) create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift create mode 100644 novawallet/Modules/Settings/Model/SettingsParameters.swift create mode 100644 novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index c717e31a76..fc7958ef5a 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -168,6 +168,7 @@ 32009DBB90D19ACD6D7B7A5C /* InAppUpdatesViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F65E56B70C5AF51A365C2BB /* InAppUpdatesViewFactory.swift */; }; 3229E306230161AA99B14BDD /* StakingRewardPayoutsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336395FFC4B2104A9651A2DE /* StakingRewardPayoutsViewFactory.swift */; }; 3250F2C0E12ED42A355853BE /* SelectValidatorsStartProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED9939B17C4224C8E153F8A /* SelectValidatorsStartProtocols.swift */; }; + 3304CC958E3E41182176769D /* WalletConnectSessionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88128EFF39CA15ABB0F598FF /* WalletConnectSessionsInteractor.swift */; }; 331186FCF0903BA793C6D930 /* DelegationListViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D8A9948F319AD0D154D1EC4 /* DelegationListViewFactory.swift */; }; 33431341505ABC30172D34E3 /* GovernanceRevokeDelegationTracksWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461A9E7672D247C9CCF0B45D /* GovernanceRevokeDelegationTracksWireframe.swift */; }; 3349B35F5D5DDD2D46FF2E48 /* LedgerInstructionsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1F9B478321689D963F51C4E /* LedgerInstructionsViewFactory.swift */; }; @@ -306,6 +307,7 @@ 61B9688494251703A6373A1B /* StakingAmountWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6216F6F1B91F798F07695FB6 /* StakingAmountWireframe.swift */; }; 61E0DC83C1D60D677274D7CE /* AccountExportPasswordViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11575D8B4F64C2E805372A5 /* AccountExportPasswordViewFactory.swift */; }; 623474C49445578F030291B0 /* ParaStkStakeSetupWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B2C32E11E2F7F3D4A1D3AB /* ParaStkStakeSetupWireframe.swift */; }; + 6252DBD2E52DB3F9459E9911 /* WalletConnectSessionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */; }; 62649D3FB6AACB508872C67A /* GovernanceUnlockConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9638E6EDBA41A5772E0033AE /* GovernanceUnlockConfirmInteractor.swift */; }; 62B2298F132DB0CE0794DD7A /* MarkdownDescriptionWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E6825E525785A1A12C62E7 /* MarkdownDescriptionWireframe.swift */; }; 63185C6D67EAEB2867069AB9 /* ParitySignerWelcomeProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBB8745F8C36BB107625E8F /* ParitySignerWelcomeProtocols.swift */; }; @@ -344,6 +346,7 @@ 6D61E43A79BDF5EA6CA9E85D /* CrowdloanListWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191A3875F3255B72E01FA92 /* CrowdloanListWireframe.swift */; }; 6D622CD4A83EEC1F135B66A8 /* ParitySignerAddConfirmWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC216C4DBF86A9F3ADB3AECF /* ParitySignerAddConfirmWireframe.swift */; }; 6D6C6FD2F13603BCE83CFC65 /* ExportMnemonicConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC566D6EACB81469B926611 /* ExportMnemonicConfirmInteractor.swift */; }; + 6DC454C4BA27C98987F5DC52 /* WalletConnectSessionsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC34A44F63EFAE6E804BDE9 /* WalletConnectSessionsViewFactory.swift */; }; 6DEA5344FAD4C6A6E7CA989C /* AdvancedWalletViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A473EB36D61D5DF3BC69F7B9 /* AdvancedWalletViewLayout.swift */; }; 6E873BD1428F104C4292FF58 /* ChainAddressDetailsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E1A4406DA1DCAA35F11C8F1 /* ChainAddressDetailsViewLayout.swift */; }; 6ECB27B386124F87382073FD /* DAppAddFavoriteProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1A00299D9B50045E1A1983 /* DAppAddFavoriteProtocols.swift */; }; @@ -370,7 +373,6 @@ 75DAB313623E900EC475E215 /* LedgerTxConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCBCB7C3ABB6C06CD4681D44 /* LedgerTxConfirmViewFactory.swift */; }; 75E689BC8D16786DF2674171 /* AssetListViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F4883B898928C77D17C824 /* AssetListViewLayout.swift */; }; 766FE2FAB8509BF0F56EA3C0 /* ParaStkCollatorInfoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3B8502E5BF8CDD7ACE2DD0 /* ParaStkCollatorInfoProtocols.swift */; }; - 76769DE386BC2590D426A92A /* WalletConnectWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */; }; 76B0B7147181747A7CEDDDF6 /* GovernanceUnavailableTracksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E25CF67173500E0AC19387 /* GovernanceUnavailableTracksViewController.swift */; }; 76CF8508C6936FC9941F3C3E /* TokensManageAddProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C995D640129977CAB05982EC /* TokensManageAddProtocols.swift */; }; 78D94A761EFECED60F38232D /* CustomValidatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 270B309EC85D8897A4ADD98A /* CustomValidatorListViewController.swift */; }; @@ -405,8 +407,6 @@ 81ADC94E1CC47A2C6F0F1BEA /* GovernanceEditDelegationTracksProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32F1F0F6F985195CD19EDDB /* GovernanceEditDelegationTracksProtocols.swift */; }; 821518375113295E41E0481C /* ParitySignerTxQrViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B56BDC7E6221DE292498D3A /* ParitySignerTxQrViewFactory.swift */; }; 8217DCBEB74527D57AC82070 /* ParaStkStakeConfirmViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10E27EB6FF31F9D247DEFABB /* ParaStkStakeConfirmViewLayout.swift */; }; - 8268156233B4789FBD9114A5 /* WalletConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */; }; - 82AD4D0342FAF7C9EB4CF2AE /* WalletConnectViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */; }; 830A27C5447348F1D202D996 /* CrowdloanContributionSetupInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23E38DCBC74C528D7839B76 /* CrowdloanContributionSetupInteractor.swift */; }; 8321399396FA5B25BC93A090 /* DAppPhishingViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22914482F786318D8F6C5E27 /* DAppPhishingViewLayout.swift */; }; 8329367F06C83BA7A0B12A34 /* CommonDelegationTracksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7235097E09C94005B091B4 /* CommonDelegationTracksPresenter.swift */; }; @@ -540,6 +540,9 @@ 8412AF992789AB76008A6C22 /* PolkadotExtensionMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8412AF982789AB76008A6C22 /* PolkadotExtensionMetadata.swift */; }; 8412AF9B2789ABBC008A6C22 /* PolkadotExtensionMetadataResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8412AF9A2789ABBC008A6C22 /* PolkadotExtensionMetadataResponse.swift */; }; 8413B27A29507F3A00F8E2E4 /* DataProviderRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8413B27929507F3900F8E2E4 /* DataProviderRepositoryStub.swift */; }; + 8413C4A42A0242B6001E190A /* WalletConnectSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8413C4A32A0242B6001E190A /* WalletConnectSession.swift */; }; + 8413C4A62A025666001E190A /* SettingsParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8413C4A52A025666001E190A /* SettingsParameters.swift */; }; + 8413C4A92A025AC3001E190A /* WalletConnectScanPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8413C4A82A025AC3001E190A /* WalletConnectScanPresentable.swift */; }; 841494402604E71C000D8D1A /* TotalRewardItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8414943F2604E71C000D8D1A /* TotalRewardItem.swift */; }; 84155DED2539817200A27058 /* ApplicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84155DEC2539817200A27058 /* ApplicationService.swift */; }; 84155DF3253A1DBA00A27058 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84155DF2253A1DBA00A27058 /* SchedulerTests.swift */; }; @@ -2939,6 +2942,7 @@ AA3AE7900853DFB4D6FC3F96 /* DelegateVotedReferendaViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399700B22225DD916DFACAF9 /* DelegateVotedReferendaViewFactory.swift */; }; AB49401A71CB6DA561C6A32A /* ParitySignerWelcomeWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B0A8174A9FFB8422A70D83 /* ParitySignerWelcomeWireframe.swift */; }; AB5E2A2B4CC823E6F6515ADD /* StakingRewardPayoutsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECD8589BD30A8BE9492AD87 /* StakingRewardPayoutsPresenter.swift */; }; + AB5EA0348C8E8C40FCA9DC86 /* WalletConnectSessionsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CC2C3981590DCEDE3ABBEC /* WalletConnectSessionsWireframe.swift */; }; ABA3D873BBECB7F4BD670872 /* ExportSeedPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFB278373745C20822442686 /* ExportSeedPresenter.swift */; }; AC904E313DC15AE40C927946 /* DAppAddFavoriteInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BE36DA0A2310660A43FF5B /* DAppAddFavoriteInteractor.swift */; }; AD1920A8DE1C9A4D6502473F /* GovernanceRemoveVotesConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6606F6FCB40D5EDA2CDFA84F /* GovernanceRemoveVotesConfirmViewController.swift */; }; @@ -3070,6 +3074,7 @@ B1B0F4818510EB082ACA83AB /* MoonbeamTermsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313CDB5EB70518DEC1BDB392 /* MoonbeamTermsViewLayout.swift */; }; B1BB78684B059A113AB3AD30 /* DAppPhishingViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AE1C39767C4CBE3229089D /* DAppPhishingViewFactory.swift */; }; B1CCC5B7BF30F6ACA309B112 /* StakingRedeemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7F5F9B54BE4234C5682BDE /* StakingRedeemViewController.swift */; }; + B1F2C2241D9020687D469A27 /* WalletConnectSessionsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82445792D5EF0120B5233767 /* WalletConnectSessionsPresenter.swift */; }; B1F86CA723BB4D69C5EF989D /* ParaStkStakeSetupProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D8246CDA02F544AF9DA2B11 /* ParaStkStakeSetupProtocols.swift */; }; B30FEC6F62918BC6F38396A2 /* ReferendumVoteConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2C32A42B0084AFF2911F6E /* ReferendumVoteConfirmViewController.swift */; }; B310C3D126C304851A40CFA9 /* ChainAddressDetailsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E1485C7B322E477D445C84 /* ChainAddressDetailsProtocols.swift */; }; @@ -3128,6 +3133,7 @@ C7D77690E10875CF1856EBA1 /* StakingRewardPayoutsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7911693957DFAF141EBDAFEC /* StakingRewardPayoutsProtocols.swift */; }; C7E68F5B6EC7B21B8797F874 /* GovernanceEditDelegationTracksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FF467EBB295AC2A3DDA4492 /* GovernanceEditDelegationTracksPresenter.swift */; }; C7E96E4185084C3E8F226E97 /* GovernanceRevokeDelegationTracksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F99FF35A16A80005A264E5F /* GovernanceRevokeDelegationTracksPresenter.swift */; }; + C8171AF2893A4723F4F63E23 /* WalletConnectSessionsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2D71C8E0A7FD346D2654C /* WalletConnectSessionsProtocols.swift */; }; C89D156BA8B690E8E4DE19ED /* ExportSeedProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1D5849A2EBF462B32F3A9C /* ExportSeedProtocols.swift */; }; C92F192D3ED6D7067298A6F4 /* ChainAddressDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB2EA5D52F51F03FBAB490FE /* ChainAddressDetailsViewController.swift */; }; C937154FA9021AECD72A871B /* StakingRewardDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C70EBF83B2547452417E588 /* StakingRewardDetailsViewController.swift */; }; @@ -3176,6 +3182,7 @@ D840B64C33EF47E723905378 /* OperationDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F05632A6635A54A9CDA7FC /* OperationDetailsViewFactory.swift */; }; D8581E5440A19D977E17BFDE /* StakingAmountViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D54DC9992CF9CB6699AA3 /* StakingAmountViewFactory.swift */; }; D886425A55425810AD070AB5 /* ControllerAccountConfirmationWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96C3B5ABF4A8124848EFD17 /* ControllerAccountConfirmationWireframe.swift */; }; + D8CB6639857FE917719EF0AF /* WalletConnectSessionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADD21F058AE84F533353158 /* WalletConnectSessionsViewController.swift */; }; D9046DBA27451ED700C29F2E /* ParallelContributionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9046DB927451ED700C29F2E /* ParallelContributionSource.swift */; }; D9046DBC27453D5C00C29F2E /* ParallelContributionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9046DBB27453D5C00C29F2E /* ParallelContributionResponse.swift */; }; D92FC57638EB959DB3850D3B /* DelegateVotedReferendaProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A44A00DAD3ADF020E2CB3D /* DelegateVotedReferendaProtocols.swift */; }; @@ -3235,7 +3242,7 @@ E9B2CD5127B700881A00EC1D /* TokensAddSelectNetworkInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA518E1D79D86360F145B428 /* TokensAddSelectNetworkInteractor.swift */; }; EAAB9E53189BC6394C5900D2 /* GovernanceSelectTracksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6962C8E51EB317DE3AAE4BDF /* GovernanceSelectTracksViewController.swift */; }; EAAFB082E2BB0CA418714061 /* ReferendumFullDetailsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CDCB8CF3D8EBE5DA7A5A30 /* ReferendumFullDetailsViewLayout.swift */; }; - EB11BF594D7E16A8885D47DD /* WalletConnectViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */; }; + EB11BF594D7E16A8885D47DD /* WalletConnectServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B193A261FDF933FE6C874B4E /* WalletConnectServiceFactory.swift */; }; EB20C6B406155664B981BA94 /* GovernanceYourDelegationsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46DF4E6D8DAF6913474DED5 /* GovernanceYourDelegationsInteractor.swift */; }; EB376E61CD1C39AC148DE80C /* NftListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A60B27D3A045E0DEF23775 /* NftListViewController.swift */; }; EB5F587A71CCE1F0F86154CF /* ControllerAccountViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002A29AE58EB53E915330490 /* ControllerAccountViewFactory.swift */; }; @@ -3257,6 +3264,7 @@ EFF8F905CE4E8A212FE79EE4 /* ParaStkYourCollatorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E099C1E3DC3730DD503BE /* ParaStkYourCollatorsViewController.swift */; }; F022F1444E0F75CCA42F4648 /* YourValidatorListProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31780E84948D7FE632ECB02 /* YourValidatorListProtocols.swift */; }; F040165DFF9C8D7C5CC47581 /* AddDelegationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C12FF06C3E4D642221EDCD /* AddDelegationProtocols.swift */; }; + F06129946DFEC9B7F282EB46 /* WalletConnectSessionsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E697F5EF3AD8EE7281FBF6CB /* WalletConnectSessionsViewLayout.swift */; }; F0675F495766D07473B065F7 /* CrowdloanYourContributionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3251EE6FFC95D656BA2146F4 /* CrowdloanYourContributionsInteractor.swift */; }; F0ADB63765A8EAA19D85C30B /* ReferendumVoteConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB47990BC7A594E663DAC00 /* ReferendumVoteConfirmPresenter.swift */; }; F0B74A766BF50518323AB25C /* DAppAuthConfirmWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A9D9D13EC9D921A2C8FB6D /* DAppAuthConfirmWireframe.swift */; }; @@ -3448,6 +3456,7 @@ 0988D8EC0768B03BA7C55612 /* ParaStkYieldBoostStartInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYieldBoostStartInteractor.swift; sourceTree = ""; }; 09E4E9A052F9F04A92F158D6 /* GovernanceDelegateSetupWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateSetupWireframe.swift; sourceTree = ""; }; 0AB10C8DA56E92C280D66BE8 /* WalletHistoryFilterViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletHistoryFilterViewFactory.swift; sourceTree = ""; }; + 0AC34A44F63EFAE6E804BDE9 /* WalletConnectSessionsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsViewFactory.swift; sourceTree = ""; }; 0B5072E250B7277F605855B3 /* AccountManagementProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountManagementProtocols.swift; sourceTree = ""; }; 0B556386CEEB76C95ED59897 /* ControllerAccountViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountViewController.swift; sourceTree = ""; }; 0B62C2CBCFF1865A1CA0F1B4 /* LedgerNetworkSelectionProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerNetworkSelectionProtocols.swift; sourceTree = ""; }; @@ -3491,6 +3500,7 @@ 19BFAC4CDCC826720CA1010A /* GovernanceDelegateConfirmViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateConfirmViewLayout.swift; sourceTree = ""; }; 19F47911F8734C007135137D /* DAppBrowserWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserWireframe.swift; sourceTree = ""; }; 1A7B39A61DB0D2F0F1B1DBA1 /* AccountCreateInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountCreateInteractor.swift; sourceTree = ""; }; + 1AE2D71C8E0A7FD346D2654C /* WalletConnectSessionsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsProtocols.swift; sourceTree = ""; }; 1B249C5D43CB86CF62C165F8 /* AssetListWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetListWireframe.swift; sourceTree = ""; }; 1BE8644A4F6DED808248A0FE /* YourValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourValidatorListViewFactory.swift; sourceTree = ""; }; 1C070C21CB13E628F8E5AEBF /* CreateWatchOnlyWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CreateWatchOnlyWireframe.swift; sourceTree = ""; }; @@ -3622,6 +3632,7 @@ 3B1D64215349613366E49F8F /* DelegationListInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationListInteractor.swift; sourceTree = ""; }; 3B5E099C1E3DC3730DD503BE /* ParaStkYourCollatorsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYourCollatorsViewController.swift; sourceTree = ""; }; 3B6244A9538B39AFCD3A6F3A /* SelectValidatorsStartPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsStartPresenter.swift; sourceTree = ""; }; + 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsTests.swift; sourceTree = ""; }; 3B8473AA386E1AD6F0F0C964 /* ControllerAccountConfirmationInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountConfirmationInteractor.swift; sourceTree = ""; }; 3BB7BFB2477E9F3893126931 /* DAppOperationConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppOperationConfirmViewFactory.swift; sourceTree = ""; }; 3BEEFB03BBA45F5143484398 /* ParaStkRedeemViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkRedeemViewFactory.swift; sourceTree = ""; }; @@ -3690,6 +3701,7 @@ 5278A5F4178922A240590334 /* DAppBrowserViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserViewLayout.swift; sourceTree = ""; }; 52B7577593D1A0789B60FF70 /* NftListViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftListViewLayout.swift; sourceTree = ""; }; 52BF275FC06E8B06FA4D4719 /* LedgerInstructionsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsViewController.swift; sourceTree = ""; }; + 52CC2C3981590DCEDE3ABBEC /* WalletConnectSessionsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsWireframe.swift; sourceTree = ""; }; 52E748EFA7E0E8B5E5CC9FEA /* DAppPhishingViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppPhishingViewController.swift; sourceTree = ""; }; 52F8D055D0481469073AA859 /* StakingPayoutConfirmationProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingPayoutConfirmationProtocols.swift; sourceTree = ""; }; 53235E51143C6E93303E30FE /* DAppSearchViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSearchViewController.swift; sourceTree = ""; }; @@ -3750,7 +3762,6 @@ 6216F6F1B91F798F07695FB6 /* StakingAmountWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingAmountWireframe.swift; sourceTree = ""; }; 627CE71CD8C7CE59B8AFBAD6 /* CommonDelegationTracksProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CommonDelegationTracksProtocols.swift; sourceTree = ""; }; 62C5AF7E89A8C6CFF5AE03B1 /* YourWalletsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourWalletsProtocols.swift; sourceTree = ""; }; - 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewLayout.swift; sourceTree = ""; }; 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportSeedViewFactory.swift; sourceTree = ""; }; 638A65DAC86BAF9EB4D2F2F8 /* StakingRewardDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardDetailsWireframe.swift; sourceTree = ""; }; 6475F9C6C6B095B9C5026CE9 /* ParaStkYieldBoostStartViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYieldBoostStartViewController.swift; sourceTree = ""; }; @@ -3808,7 +3819,6 @@ 793046EA14E4CAB096803BCD /* NftDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftDetailsWireframe.swift; sourceTree = ""; }; 79BC90C31B17F8935FE8456F /* Pods-novawalletAll-novawallet.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-novawalletAll-novawallet.dev.xcconfig"; path = "Target Support Files/Pods-novawalletAll-novawallet/Pods-novawalletAll-novawallet.dev.xcconfig"; sourceTree = ""; }; 7A092ADC09DA0429548EBC08 /* NftListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftListPresenter.swift; sourceTree = ""; }; - 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewController.swift; sourceTree = ""; }; 7ACF32611D345B87BCE29FE0 /* DAppAddFavoriteWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAddFavoriteWireframe.swift; sourceTree = ""; }; 7B13D65B93E65B5112272962 /* DelegationReferendumVotersWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationReferendumVotersWireframe.swift; sourceTree = ""; }; 7B1A00299D9B50045E1A1983 /* DAppAddFavoriteProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAddFavoriteProtocols.swift; sourceTree = ""; }; @@ -3834,6 +3844,7 @@ 818D1B632559444D08303469 /* ParaStkSelectCollatorsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkSelectCollatorsViewController.swift; sourceTree = ""; }; 81F4883B898928C77D17C824 /* AssetListViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetListViewLayout.swift; sourceTree = ""; }; 8202B83B2DF36439CB6449C6 /* TransferSetupViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransferSetupViewFactory.swift; sourceTree = ""; }; + 82445792D5EF0120B5233767 /* WalletConnectSessionsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsPresenter.swift; sourceTree = ""; }; 837E9D1F8096A0E9CA0E0CEB /* MoonbeamTermsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoonbeamTermsPresenter.swift; sourceTree = ""; }; 83AB0AD3A7CECD061611F60C /* AssetSelectionInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetSelectionInteractor.swift; sourceTree = ""; }; 84002A962990491A00A80672 /* GovernanceRemoveVotesInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceRemoveVotesInteractorError.swift; sourceTree = ""; }; @@ -3964,6 +3975,9 @@ 8412AF982789AB76008A6C22 /* PolkadotExtensionMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotExtensionMetadata.swift; sourceTree = ""; }; 8412AF9A2789ABBC008A6C22 /* PolkadotExtensionMetadataResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotExtensionMetadataResponse.swift; sourceTree = ""; }; 8413B27929507F3900F8E2E4 /* DataProviderRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProviderRepositoryStub.swift; sourceTree = ""; }; + 8413C4A32A0242B6001E190A /* WalletConnectSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSession.swift; sourceTree = ""; }; + 8413C4A52A025666001E190A /* SettingsParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsParameters.swift; sourceTree = ""; }; + 8413C4A82A025AC3001E190A /* WalletConnectScanPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectScanPresentable.swift; sourceTree = ""; }; 8414943F2604E71C000D8D1A /* TotalRewardItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalRewardItem.swift; sourceTree = ""; }; 84155DEC2539817200A27058 /* ApplicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationService.swift; sourceTree = ""; }; 84155DF2253A1DBA00A27058 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = ""; }; @@ -6074,6 +6088,7 @@ 880E4102298CFFBF0077B18B /* DelegationsDisplayStringFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegationsDisplayStringFactory.swift; sourceTree = ""; }; 880E4104298D0E550077B18B /* GovernanceDelegationsLocalWrapperFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceDelegationsLocalWrapperFactory.swift; sourceTree = ""; }; 88107D6029015FAB001AB0B0 /* TrackTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackTagsView.swift; sourceTree = ""; }; + 88128EFF39CA15ABB0F598FF /* WalletConnectSessionsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsInteractor.swift; sourceTree = ""; }; 8813702B29C13E7900829458 /* Web3NamesOperationFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3NamesOperationFactoryTests.swift; sourceTree = ""; }; 8814773B29AF554D001E98A1 /* AddDelegationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDelegationViewModel.swift; sourceTree = ""; }; 8814773D29B071AD001E98A1 /* ImageViewModelProtocol+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageViewModelProtocol+Optional.swift"; sourceTree = ""; }; @@ -6356,6 +6371,7 @@ 999C15317E0B4FC67B9C17C5 /* StakingAmountProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingAmountProtocols.swift; sourceTree = ""; }; 99C973B203F72D6233718CD4 /* ParaStkUnstakeConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkUnstakeConfirmProtocols.swift; sourceTree = ""; }; 99F4AC351A494FC4A89A6CF6 /* DelegationReferendumVotersProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationReferendumVotersProtocols.swift; sourceTree = ""; }; + 9ADD21F058AE84F533353158 /* WalletConnectSessionsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsViewController.swift; sourceTree = ""; }; 9B5626189788682A84D4E9D7 /* SelectValidatorsConfirmPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsConfirmPresenter.swift; sourceTree = ""; }; 9B754B68D6F1D1ED5C8577A5 /* AssetListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetListViewFactory.swift; sourceTree = ""; }; 9BCCD837A377C237C18B117E /* OperationDetailsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OperationDetailsViewController.swift; sourceTree = ""; }; @@ -6541,7 +6557,7 @@ B0B2C32E11E2F7F3D4A1D3AB /* ParaStkStakeSetupWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupWireframe.swift; sourceTree = ""; }; B14E9691A7628B66958F8744 /* LedgerInstructionsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsProtocols.swift; sourceTree = ""; }; B18E8361691E548ABAB33EA4 /* GovernanceEditDelegationTracksViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceEditDelegationTracksViewController.swift; sourceTree = ""; }; - B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectViewFactory.swift; sourceTree = ""; }; + B193A261FDF933FE6C874B4E /* WalletConnectServiceFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectServiceFactory.swift; sourceTree = ""; }; B1AC788C6E0A621B6D88D1BC /* ParaStkStakeSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupPresenter.swift; sourceTree = ""; }; B1F9B478321689D963F51C4E /* LedgerInstructionsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsViewFactory.swift; sourceTree = ""; }; B213C270130EDCF51303BFBE /* DAppAuthSettingsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAuthSettingsPresenter.swift; sourceTree = ""; }; @@ -6680,6 +6696,7 @@ E5CC1FB277A878E9C9B7EAEB /* AccountImportInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountImportInteractor.swift; sourceTree = ""; }; E63ECD1205B2CCCDA2E66A1E /* ParaStkYieldBoostSetupViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYieldBoostSetupViewLayout.swift; sourceTree = ""; }; E675B4C5BE36C0004564105B /* DAppOperationConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppOperationConfirmProtocols.swift; sourceTree = ""; }; + E697F5EF3AD8EE7281FBF6CB /* WalletConnectSessionsViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsViewLayout.swift; sourceTree = ""; }; E6DDBA4DD86CEFAAE236B23B /* GovernanceDelegateSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateSetupPresenter.swift; sourceTree = ""; }; E6FE9E98CB265815986BE909 /* ReferendumVotersProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVotersProtocols.swift; sourceTree = ""; }; E765FD856EB60BBF344205F3 /* TokensAddSelectNetworkWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensAddSelectNetworkWireframe.swift; sourceTree = ""; }; @@ -6833,7 +6850,6 @@ F4FDA0FC26A57860003D753B /* EraCountdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraCountdown.swift; sourceTree = ""; }; F52B8815D6AF5E69B145D245 /* CustomValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CustomValidatorListViewFactory.swift; sourceTree = ""; }; F561E2D27FCEF7DEE3FE0B3D /* InAppUpdatesViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InAppUpdatesViewController.swift; sourceTree = ""; }; - F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectWireframe.swift; sourceTree = ""; }; F5E4CD58A9006CEB045E8977 /* ChangeWatchOnlyInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChangeWatchOnlyInteractor.swift; sourceTree = ""; }; F61D8973ADEB461DE2AD3E13 /* RecommendedValidatorListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewController.swift; sourceTree = ""; }; F63700316ADAC007DD318EC6 /* TransferSetupViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransferSetupViewLayout.swift; sourceTree = ""; }; @@ -7099,16 +7115,12 @@ 1398F9A5DE987487202A580E /* WalletConnect */ = { isa = PBXGroup; children = ( + 8413C4A72A025AB0001E190A /* Protocols */, + 84E8BA2D2A02207300FD9F40 /* Service */, + EBA3D6C1965795AF75E2A18B /* Sessions */, 84B8AA7729F9049900347A37 /* States */, 84B8AA7629F9048F00347A37 /* Transport */, 849F33B529F7B225001AEFA4 /* Model */, - 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */, - F59F695F604CC497490CB491 /* WalletConnectWireframe.swift */, - 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */, - 5DDDB8BE848C65B65FE4086D /* WalletConnectInteractor.swift */, - 7A47344DCE0FA8D98DDA35CC /* WalletConnectViewController.swift */, - 62D1EB228801CBD2272B3AB3 /* WalletConnectViewLayout.swift */, - B193A261FDF933FE6C874B4E /* WalletConnectViewFactory.swift */, ); path = WalletConnect; sourceTree = ""; @@ -8059,6 +8071,14 @@ path = Governance; sourceTree = ""; }; + 8413C4A72A025AB0001E190A /* Protocols */ = { + isa = PBXGroup; + children = ( + 8413C4A82A025AC3001E190A /* WalletConnectScanPresentable.swift */, + ); + path = Protocols; + sourceTree = ""; + }; 84155DE8253980D700A27058 /* Services */ = { isa = PBXGroup; children = ( @@ -11552,6 +11572,7 @@ isa = PBXGroup; children = ( 8428765E24ADE0BB00D91AD8 /* UserSettings.swift */, + 8413C4A52A025666001E190A /* SettingsParameters.swift */, ); path = Model; sourceTree = ""; @@ -11878,6 +11899,7 @@ 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */, 8485034F29FBC84300AE6909 /* WalletConnectSignModelFactory.swift */, 84E8BA2729FFCA4000FD9F40 /* WalletConnectEthereumTransaction.swift */, + 8413C4A32A0242B6001E190A /* WalletConnectSession.swift */, ); path = Model; sourceTree = ""; @@ -12234,6 +12256,7 @@ 84B7C705289BFA79001A3566 /* AccountManagement */, 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, + FEEA0BAB955E266213341299 /* WalletConnectSessions */, ); path = Modules; sourceTree = ""; @@ -13842,6 +13865,17 @@ path = Ethereum; sourceTree = ""; }; + 84E8BA2D2A02207300FD9F40 /* Service */ = { + isa = PBXGroup; + children = ( + 60F6D1F16B35354844E435AC /* WalletConnectProtocols.swift */, + 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */, + 5DDDB8BE848C65B65FE4086D /* WalletConnectInteractor.swift */, + B193A261FDF933FE6C874B4E /* WalletConnectServiceFactory.swift */, + ); + path = Service; + sourceTree = ""; + }; 84EBC54424F660A700459D15 /* EventCenter */ = { isa = PBXGroup; children = ( @@ -15907,6 +15941,20 @@ path = RevokeDelegationTracks; sourceTree = ""; }; + EBA3D6C1965795AF75E2A18B /* Sessions */ = { + isa = PBXGroup; + children = ( + 1AE2D71C8E0A7FD346D2654C /* WalletConnectSessionsProtocols.swift */, + 52CC2C3981590DCEDE3ABBEC /* WalletConnectSessionsWireframe.swift */, + 82445792D5EF0120B5233767 /* WalletConnectSessionsPresenter.swift */, + 88128EFF39CA15ABB0F598FF /* WalletConnectSessionsInteractor.swift */, + 9ADD21F058AE84F533353158 /* WalletConnectSessionsViewController.swift */, + E697F5EF3AD8EE7281FBF6CB /* WalletConnectSessionsViewLayout.swift */, + 0AC34A44F63EFAE6E804BDE9 /* WalletConnectSessionsViewFactory.swift */, + ); + path = Sessions; + sourceTree = ""; + }; F14E378DE1BCBB4C8D39AB4C /* ParaStkYieldBoostStop */ = { isa = PBXGroup; children = ( @@ -16332,6 +16380,14 @@ path = GovernanceUnlockSetup; sourceTree = ""; }; + FEEA0BAB955E266213341299 /* WalletConnectSessions */ = { + isa = PBXGroup; + children = ( + 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */, + ); + path = WalletConnectSessions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -17609,6 +17665,7 @@ 8490895428D4BA7C00C3CCE9 /* CustomSiMappers.swift in Sources */, 84100F3A26A60B0900A5054E /* YourValidatorListStatusSectionView.swift in Sources */, 8448D5B8277DA4C200FAEEBC /* DAppListLoadingView.swift in Sources */, + 8413C4A92A025AC3001E190A /* WalletConnectScanPresentable.swift in Sources */, 84F30EEC25FFF40C00039D09 /* Nomination.swift in Sources */, 84D2F1A227730F3A0040C680 /* DAppOperationConfirmViewModelFactory.swift in Sources */, AE89720D25F12AF8008EC414 /* ValidatorInfoViewModelFactory.swift in Sources */, @@ -18176,6 +18233,7 @@ 8454C2652632B0EF00657DAD /* EventCodingPath.swift in Sources */, 843910D4253F8DAD00E3C217 /* NetworkAvailabilityLayerPresenter.swift in Sources */, 84BFE89928C2320700140F1F /* ParaStkYieldBoostQuery.swift in Sources */, + 8413C4A62A025666001E190A /* SettingsParameters.swift in Sources */, AEE5FB1226457A88002B8FDC /* StakingRewardDestSetupWireframe.swift in Sources */, 8467FD2A24EA6C62005D486C /* SigningWrapper.swift in Sources */, 88F7716028BEA589008C028A /* YourWalletsIconDetailsView.swift in Sources */, @@ -18233,6 +18291,7 @@ 8430AB002602338D005B1066 /* PendingNominatorState.swift in Sources */, 84B73ADA279D265A0071AE16 /* AssetsSubscriptionHandlingFactory.swift in Sources */, AE7129BC2608C069000AA3F5 /* NetworkStakingInfoOperationFactory.swift in Sources */, + 8413C4A42A0242B6001E190A /* WalletConnectSession.swift in Sources */, 842A736827DB4883006EE1EA /* OperationTransferModel.swift in Sources */, 8881043A29EBDC48000FA9BC /* EquilibriumTokenExtras.swift in Sources */, AE2C84E425EF98C700986716 /* ValidatorInfoViewController.swift in Sources */, @@ -19946,12 +20005,16 @@ 09AB6DE2D19F1FA36BF08288 /* StakingRebagConfirmViewLayout.swift in Sources */, 4A24646D497B26E51926BA52 /* StakingRebagConfirmViewFactory.swift in Sources */, 51FB88BCF778805D142DD8A9 /* WalletConnectProtocols.swift in Sources */, - 76769DE386BC2590D426A92A /* WalletConnectWireframe.swift in Sources */, 5619C10BFFAEAF89883227B4 /* WalletConnectPresenter.swift in Sources */, 2EDE38E0F2E3494D16717A74 /* WalletConnectInteractor.swift in Sources */, - 8268156233B4789FBD9114A5 /* WalletConnectViewController.swift in Sources */, - 82AD4D0342FAF7C9EB4CF2AE /* WalletConnectViewLayout.swift in Sources */, - EB11BF594D7E16A8885D47DD /* WalletConnectViewFactory.swift in Sources */, + EB11BF594D7E16A8885D47DD /* WalletConnectServiceFactory.swift in Sources */, + C8171AF2893A4723F4F63E23 /* WalletConnectSessionsProtocols.swift in Sources */, + AB5EA0348C8E8C40FCA9DC86 /* WalletConnectSessionsWireframe.swift in Sources */, + B1F2C2241D9020687D469A27 /* WalletConnectSessionsPresenter.swift in Sources */, + 3304CC958E3E41182176769D /* WalletConnectSessionsInteractor.swift in Sources */, + D8CB6639857FE917719EF0AF /* WalletConnectSessionsViewController.swift in Sources */, + F06129946DFEC9B7F282EB46 /* WalletConnectSessionsViewLayout.swift in Sources */, + 6DC454C4BA27C98987F5DC52 /* WalletConnectSessionsViewFactory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -20112,6 +20175,7 @@ 84B7C748289BFA79001A3566 /* WalletListTests.swift in Sources */, 84B7C720289BFA79001A3566 /* ReferralCrowdloanTests.swift in Sources */, F4897BB126AED13D0075F291 /* EraCountdownOperationFactoryStub.swift in Sources */, + 6252DBD2E52DB3F9459E9911 /* WalletConnectSessionsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/Contents.json b/novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/Contents.json new file mode 100644 index 0000000000..2cb9c97d37 --- /dev/null +++ b/novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iconWalletConnect.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/iconWalletConnect.pdf b/novawallet/Assets.xcassets/iconsSettings/iconWalletConnect.imageset/iconWalletConnect.pdf new file mode 100644 index 0000000000000000000000000000000000000000..85efd1bdb34ad1390ce31ee85d93e4f92eeb45c7 GIT binary patch literal 2072 zcmZvd!Hyd@42JJ|3SAaR4v|Gklqetw(Cjur&=%P^y#+nEvy+Vrucx)0BJI=X=XflS z-Oj-neJrLNi^ zcKzzfEbjWh*ImDS``X@qod4_g<}X9ekB{}Imly4=eG8{=$yFb-6x%%+?YH zi`ce)-)&8DzNQq|l6S>Z^wB<<;v#bu!04o=%-&LPIVF$JGsf75GtG@VL)nEYbfl4T zt-)@Trc8vGun}>IKG`#?*hZ3W(~PStQA!RL9+3}o^pq)p zMaevd$1a3vUG%xcVA**om6EP%#OKKBYqIN1bQuVeA%9N+`ES4&DP#eY>?>qyM8!lZ z5VIKqg;G-`DEB|r-IynK5et^jfNzqnsr20}hp6#?V(RmpqNQkV+7#UcJ&Q6QxlaE@G{fHA|WmQkC*n9o~`GmY`h(5l6T?g%Q?EL=q>%UI7TEBF%} zWY}X0Pg@%P#06$bI&-QLUVxY?lU$(dY3MK{pUu{MFnWkjC-mt!*OU8r%%cyeo${$+ zoc@a+w%g&bx1WB)ZGdO->py??c5%OY>?iO`|FB-Y9=_R6=z{CRJzyq|=l%KZ9<|~*`Q$Nn5+KUtOvaXo*oo_e6!nzBI4Ai zA>sfjt`KT{g$nxW8lj0!=&)O@H~r3N_1@h ServiceCoordinatorProtocol { let githubPhishingAPIService = GitHubPhishingServiceFactory.createService() @@ -153,7 +161,8 @@ extension ServiceCoordinator { evmAssetsService: evmAssetsService, evmNativeService: evmNativeService, githubPhishingService: githubPhishingAPIService, - equilibriumService: equilibriumService + equilibriumService: equilibriumService, + dappMediator: DAppInteractionFactory.createMediator() ) } } diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index 60bd669522..b6693f25fa 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -17,6 +17,8 @@ protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { func submit(proposalDecision: WalletConnectProposalDecision) func submit(signingDecision: WalletConnectSignDecision) + + func getSessions() -> [Session] } enum WalletConnectServiceError: Error { @@ -221,6 +223,14 @@ extension WalletConnectService: WalletConnectServiceProtocol { } } + func getSessions() -> [Session] { + guard let client = client else { + return [] + } + + return client.getSessions() + } + func setup() { setupNetworking() setupPairing() diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift index e73a48950e..3466302020 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift @@ -12,10 +12,18 @@ final class DAppInteractionFactory { let phishingVerifier = PhishingSiteVerifier.createSequentialVerifier() + let chainsStore = ChainsStore(chainRegistry: ChainRegistryFacade.sharedRegistry) + + let walletConnect = WalletConnectServiceFactory.createInteractor( + chainsStore: chainsStore, + settingsRepository: settingsRepository, + operationQueue: OperationManagerFacade.sharedDefaultQueue + ) + let mediator = DAppInteractionMediator( presenter: presenter, - children: [], - chainRegistry: ChainRegistryFacade.sharedRegistry, + children: [walletConnect], + chainsStore: chainsStore, settingsRepository: settingsRepository, securedLayer: SecurityLayerService.shared, sequentialPhishingVerifier: phishingVerifier, @@ -24,6 +32,7 @@ final class DAppInteractionFactory { ) presenter.interactor = mediator + walletConnect.mediator = mediator return mediator } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift index 0ea3f79e88..ff9b28e760 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift @@ -25,7 +25,7 @@ final class DAppInteractionMediator { init( presenter: DAppInteractionOutputProtocol, children: [DAppInteractionChildProtocol], - chainRegistry: ChainRegistryProtocol, + chainsStore: ChainsStoreProtocol, settingsRepository: AnyDataProviderRepository, securedLayer: SecurityLayerServiceProtocol, sequentialPhishingVerifier: PhishingSiteVerifing, @@ -34,7 +34,7 @@ final class DAppInteractionMediator { ) { self.presenter = presenter self.children = children - chainsStore = ChainsStore(chainRegistry: chainRegistry) + self.chainsStore = chainsStore self.settingsRepository = settingsRepository self.securedLayer = securedLayer self.sequentialPhishingVerifier = sequentialPhishingVerifier diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift index 8f8e0dbd1c..cfd98306b1 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift @@ -15,11 +15,13 @@ protocol DAppTransportProtocol: AnyObject { func stop() } -protocol DAppInteractionMediating: ApplicationServiceProtocol { +protocol DAppInteractionMediating: AnyObject, ApplicationServiceProtocol { var chainsStore: ChainsStoreProtocol { get } var settingsRepository: AnyDataProviderRepository { get } var operationQueue: OperationQueue { get } + var children: [DAppInteractionChildProtocol] { get } + func register(transport: DAppTransportProtocol) func unregister(transport: DAppTransportProtocol) @@ -41,4 +43,6 @@ protocol DAppInteractionOutputProtocol: AnyObject { func didReceive(error: DAppInteractionError) } -protocol DAppInteractionChildProtocol: ApplicationServiceProtocol {} +protocol DAppInteractionChildProtocol: ApplicationServiceProtocol { + var mediator: DAppInteractionMediating? { get set } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift new file mode 100644 index 0000000000..1d3de36efe --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift @@ -0,0 +1,10 @@ +import Foundation + +struct WalletConnectSession { + let sessionId: String + let wallet: MetaAccountModel? + let dAppName: String? + let dAppHost: String? + let dAppIcon: URL? + let isDAppActive: Bool +} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift similarity index 52% rename from novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift rename to novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift index bb58243286..b7add91e32 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectWireframe.swift +++ b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift @@ -1,7 +1,12 @@ import Foundation -final class WalletConnectWireframe: WalletConnectWireframeProtocol { - func showScan(from view: WalletConnectViewProtocol?, delegate: URIScanDelegate) { +protocol WalletConnectScanPresentable: AnyObject { + func showScan(from view: ControllerBackedProtocol?, delegate: URIScanDelegate) + func hideUriScanAnimated(from view: ControllerBackedProtocol?, completion: @escaping () -> Void) +} + +extension WalletConnectScanPresentable { + func showScan(from view: ControllerBackedProtocol?, delegate: URIScanDelegate) { guard let scanView = URIScanViewFactory.createScan(for: delegate, context: nil) else { return } diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift new file mode 100644 index 0000000000..41923ac80b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -0,0 +1,96 @@ +import UIKit +import WalletConnectSwiftV2 + +final class WalletConnectInteractor { + let presenter: WalletConnectInteractorOutputProtocol + let transport: WalletConnectTransportProtocol + + weak var mediator: DAppInteractionMediating? + + private var delegates: [WeakWrapper] = [] + + init( + transport: WalletConnectTransportProtocol, + presenter: WalletConnectInteractorOutputProtocol + ) { + self.transport = transport + self.presenter = presenter + } +} + +extension WalletConnectInteractor: WalletConnectInteractorInputProtocol {} + +extension WalletConnectInteractor: WalletConnectDelegateInputProtocol { + func connect(uri: String) { + transport.connect(uri: uri) + } + + func add(delegate: WalletConnectDelegateOutputProtocol) { + remove(delegate: delegate) + + delegates.append(WeakWrapper(target: delegate)) + } + + func remove(delegate: WalletConnectDelegateOutputProtocol) { + delegates = delegates.filter { wrapper in + wrapper.target != nil && wrapper.target !== delegate + } + } + + func getSessionsCount() -> Int { + transport.getSessionsCount() + } +} + +extension WalletConnectInteractor: WalletConnectTransportDelegate { + func walletConnect( + transport: WalletConnectTransportProtocol, + didReceive message: WalletConnectTransportMessage + ) { + mediator?.process(message: message, host: message.host, transport: transport.name) + } + + func walletConnect( + transport _: WalletConnectTransportProtocol, + didFail _: WalletConnectTransportError + ) { + // TODO: Handle error + } + + func walletConnect(transport _: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) { + mediator?.process(authRequest: request) + } + + func walletConnect( + transport _: WalletConnectTransportProtocol, + sign request: DAppOperationRequest, + type: DAppSigningType + ) { + mediator?.process(signingRequest: request, type: type) + } + + func walletConnectDidChangeSessions(transport _: WalletConnectTransportProtocol) { + delegates.forEach { wrapper in + guard let target = wrapper.target as? WalletConnectDelegateOutputProtocol else { + return + } + + return target.walletConnectDidChangeSessions() + } + } + + func walletConnectAskNextMessage(transport _: WalletConnectTransportProtocol) { + mediator?.processMessageQueue() + } +} + +extension WalletConnectInteractor: DAppInteractionChildProtocol { + func setup() { + transport.delegate = self + mediator?.register(transport: transport) + } + + func throttle() { + mediator?.unregister(transport: transport) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift new file mode 100644 index 0000000000..bd629fab29 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift @@ -0,0 +1,13 @@ +import Foundation + +final class WalletConnectPresenter { + weak var interactor: WalletConnectInteractorInputProtocol? + + let logger: LoggerProtocol + + init(logger: LoggerProtocol) { + self.logger = logger + } +} + +extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol {} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift new file mode 100644 index 0000000000..6c688f29cb --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift @@ -0,0 +1,16 @@ +protocol WalletConnectInteractorInputProtocol: AnyObject {} + +protocol WalletConnectInteractorOutputProtocol: AnyObject {} + +protocol WalletConnectDelegateInputProtocol: AnyObject { + func connect(uri: String) + + func add(delegate: WalletConnectDelegateOutputProtocol) + func remove(delegate: WalletConnectDelegateOutputProtocol) + + func getSessionsCount() -> Int +} + +protocol WalletConnectDelegateOutputProtocol: AnyObject { + func walletConnectDidChangeSessions() +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift new file mode 100644 index 0000000000..e6c906251b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift @@ -0,0 +1,31 @@ +import Foundation +import RobinHood + +struct WalletConnectServiceFactory { + static func createInteractor( + chainsStore: ChainsStoreProtocol, + settingsRepository: AnyDataProviderRepository, + operationQueue: OperationQueue + ) -> WalletConnectInteractor { + let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) + let service = WalletConnectService(metadata: metadata) + + let dataSource = DAppStateDataSource( + chainsStore: chainsStore, + dAppSettingsRepository: settingsRepository, + walletSettings: SelectedWalletSettings.shared, + operationQueue: operationQueue + ) + + let transport = WalletConnectTransport( + service: service, + dataSource: dataSource, + logger: Logger.shared + ) + + return .init( + transport: transport, + presenter: WalletConnectPresenter(logger: Logger.shared) + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift new file mode 100644 index 0000000000..fc401f0387 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift @@ -0,0 +1,25 @@ +import UIKit + +final class WalletConnectSessionsInteractor { + weak var presenter: WalletConnectSessionsInteractorOutputProtocol? + + let walletConnect: WalletConnectDelegateInputProtocol + + init(walletConnect: WalletConnectDelegateInputProtocol) { + self.walletConnect = walletConnect + } +} + +extension WalletConnectSessionsInteractor: WalletConnectSessionsInteractorInputProtocol { + func setup() { + walletConnect.add(delegate: self) + } + + func connect(uri: String) { + walletConnect.connect(uri: uri) + } +} + +extension WalletConnectSessionsInteractor: WalletConnectDelegateOutputProtocol { + func walletConnectDidChangeSessions() {} +} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift similarity index 50% rename from novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift rename to novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift index 09cb9bcadf..bcbc878322 100644 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift @@ -1,15 +1,15 @@ import Foundation -final class WalletConnectPresenter { - weak var view: WalletConnectViewProtocol? - let wireframe: WalletConnectWireframeProtocol - let interactor: WalletConnectInteractorInputProtocol +final class WalletConnectSessionsPresenter { + weak var view: WalletConnectSessionsViewProtocol? + let wireframe: WalletConnectSessionsWireframeProtocol + let interactor: WalletConnectSessionsInteractorInputProtocol let logger: LoggerProtocol init( - interactor: WalletConnectInteractorInputProtocol, - wireframe: WalletConnectWireframeProtocol, + interactor: WalletConnectSessionsInteractorInputProtocol, + wireframe: WalletConnectSessionsWireframeProtocol, logger: LoggerProtocol ) { self.interactor = interactor @@ -18,7 +18,7 @@ final class WalletConnectPresenter { } } -extension WalletConnectPresenter: WalletConnectPresenterProtocol { +extension WalletConnectSessionsPresenter: WalletConnectSessionsPresenterProtocol { func setup() { interactor.setup() } @@ -28,9 +28,9 @@ extension WalletConnectPresenter: WalletConnectPresenterProtocol { } } -extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol {} +extension WalletConnectSessionsPresenter: WalletConnectSessionsInteractorOutputProtocol {} -extension WalletConnectPresenter: URIScanDelegate { +extension WalletConnectSessionsPresenter: URIScanDelegate { func uriScanDidReceive(uri: String, context _: AnyObject?) { logger.debug("Wallet Connect URI: \(uri)") diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift new file mode 100644 index 0000000000..d12cf3f359 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift @@ -0,0 +1,15 @@ +protocol WalletConnectSessionsViewProtocol: ControllerBackedProtocol {} + +protocol WalletConnectSessionsPresenterProtocol: AnyObject { + func setup() + func showScan() +} + +protocol WalletConnectSessionsInteractorInputProtocol: AnyObject { + func setup() + func connect(uri: String) +} + +protocol WalletConnectSessionsInteractorOutputProtocol: AnyObject {} + +protocol WalletConnectSessionsWireframeProtocol: WalletConnectScanPresentable {} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift new file mode 100644 index 0000000000..4ba17be952 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift @@ -0,0 +1,41 @@ +import UIKit + +final class WalletConnectSessionsViewController: UIViewController { + typealias RootViewType = WalletConnectSessionsViewLayout + + let presenter: WalletConnectSessionsPresenterProtocol + + init(presenter: WalletConnectSessionsPresenterProtocol) { + self.presenter = presenter + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = WalletConnectSessionsViewLayout() + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupLocalization() + + presenter.setup() + } + + private func setupHandlers() {} + + private func setupLocalization() { + title = "Your sessions" + } + + @objc func actionScan() { + presenter.showScan() + } +} + +extension WalletConnectSessionsViewController: WalletConnectSessionsViewProtocol {} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift new file mode 100644 index 0000000000..dfe72b113b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift @@ -0,0 +1,39 @@ +import Foundation + +struct WalletConnectSessionsViewFactory { + static func createView( + with dappMediator: DAppInteractionMediating + ) -> WalletConnectSessionsViewProtocol? { + guard let interactor = createInteractor(with: dappMediator) else { + return nil + } + + let wireframe = WalletConnectSessionsWireframe() + + let presenter = WalletConnectSessionsPresenter( + interactor: interactor, + wireframe: wireframe, + logger: Logger.shared + ) + + let view = WalletConnectSessionsViewController(presenter: presenter) + + presenter.view = view + interactor.presenter = presenter + + return view + } + + private static func createInteractor( + with dappMediator: DAppInteractionMediating + ) -> WalletConnectSessionsInteractor? { + guard + let walletConnect = dappMediator.children.first( + where: { $0 is WalletConnectDelegateInputProtocol } + ) as? WalletConnectDelegateInputProtocol else { + return nil + } + + return .init(walletConnect: walletConnect) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift new file mode 100644 index 0000000000..dc9c9795ef --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift @@ -0,0 +1,12 @@ +import UIKit + +final class WalletConnectSessionsViewLayout: UIView { + override init(frame: CGRect) { + super.init(frame: frame) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift new file mode 100644 index 0000000000..e4d48ff7aa --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift @@ -0,0 +1,3 @@ +import Foundation + +final class WalletConnectSessionsWireframe: WalletConnectSessionsWireframeProtocol {} diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index e1d1290f19..3999561dd2 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -5,6 +5,8 @@ protocol WalletConnectTransportProtocol: DAppTransportProtocol { var delegate: WalletConnectTransportDelegate? { get set } func connect(uri: String) + + func getSessionsCount() -> Int } protocol WalletConnectTransportDelegate: AnyObject { @@ -23,6 +25,8 @@ protocol WalletConnectTransportDelegate: AnyObject { func walletConnect(transport: WalletConnectTransportProtocol, didFail error: WalletConnectTransportError) + func walletConnectDidChangeSessions(transport: WalletConnectTransportProtocol) + func walletConnectAskNextMessage(transport: WalletConnectTransportProtocol) } @@ -50,6 +54,10 @@ extension WalletConnectTransport: WalletConnectTransportProtocol { func connect(uri: String) { service.connect(uri: uri) } + + func getSessionsCount() -> Int { + service.getSessions().count + } } extension WalletConnectTransport { @@ -165,7 +173,7 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { logger.debug("New session: \(establishedSession)") - // TODO: Handle session in ui task + delegate?.walletConnectDidChangeSessions(transport: self) } func walletConnect(service _: WalletConnectServiceProtocol, request: Request, session: Session?) { diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift deleted file mode 100644 index 30b73611b4..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectInteractor.swift +++ /dev/null @@ -1,61 +0,0 @@ -import UIKit -import WalletConnectSwiftV2 - -final class WalletConnectInteractor { - weak var presenter: WalletConnectInteractorOutputProtocol? - - let transport: WalletConnectTransportProtocol - let mediator: DAppInteractionMediating - - init(mediator: DAppInteractionMediating, transport: WalletConnectTransportProtocol) { - self.mediator = mediator - self.transport = transport - } - - deinit { - mediator.unregister(transport: transport) - } -} - -extension WalletConnectInteractor: WalletConnectInteractorInputProtocol { - func setup() { - transport.delegate = self - mediator.register(transport: transport) - } - - func connect(uri: String) { - transport.connect(uri: uri) - } -} - -extension WalletConnectInteractor: WalletConnectTransportDelegate { - func walletConnect( - transport: WalletConnectTransportProtocol, - didReceive message: WalletConnectTransportMessage - ) { - mediator.process(message: message, host: message.host, transport: transport.name) - } - - func walletConnect( - transport _: WalletConnectTransportProtocol, - didFail _: WalletConnectTransportError - ) { - // TODO: Handle error - } - - func walletConnect(transport _: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) { - mediator.process(authRequest: request) - } - - func walletConnect( - transport _: WalletConnectTransportProtocol, - sign request: DAppOperationRequest, - type: DAppSigningType - ) { - mediator.process(signingRequest: request, type: type) - } - - func walletConnectAskNextMessage(transport _: WalletConnectTransportProtocol) { - mediator.processMessageQueue() - } -} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift deleted file mode 100644 index 1f5fd51507..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectProtocols.swift +++ /dev/null @@ -1,18 +0,0 @@ -protocol WalletConnectViewProtocol: ControllerBackedProtocol {} - -protocol WalletConnectPresenterProtocol: AnyObject { - func setup() - func showScan() -} - -protocol WalletConnectInteractorInputProtocol: AnyObject { - func setup() - func connect(uri: String) -} - -protocol WalletConnectInteractorOutputProtocol: AnyObject {} - -protocol WalletConnectWireframeProtocol: AnyObject { - func showScan(from view: WalletConnectViewProtocol?, delegate: URIScanDelegate) - func hideUriScanAnimated(from view: ControllerBackedProtocol?, completion: @escaping () -> Void) -} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift deleted file mode 100644 index cd2c6a6866..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewController.swift +++ /dev/null @@ -1,47 +0,0 @@ -import UIKit - -final class WalletConnectViewController: UIViewController, ViewHolder { - typealias RootViewType = WalletConnectViewLayout - - let presenter: WalletConnectPresenterProtocol - - init(presenter: WalletConnectPresenterProtocol) { - self.presenter = presenter - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = WalletConnectViewLayout() - } - - override func viewDidLoad() { - super.viewDidLoad() - - setupScanItem() - setupLocalization() - - presenter.setup() - } - - private func setupScanItem() { - navigationItem.rightBarButtonItem = rootView.scanItem - - rootView.scanItem.target = self - rootView.scanItem.action = #selector(actionScan) - } - - private func setupLocalization() { - title = "Your sessions" - } - - @objc func actionScan() { - presenter.showScan() - } -} - -extension WalletConnectViewController: WalletConnectViewProtocol {} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift deleted file mode 100644 index 3f3d490e30..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewFactory.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -struct WalletConnectViewFactory { - static func createView() -> WalletConnectViewProtocol? { - let interactor = createInteractor() - let wireframe = WalletConnectWireframe() - - let presenter = WalletConnectPresenter( - interactor: interactor, - wireframe: wireframe, - logger: Logger.shared - ) - - let view = WalletConnectViewController(presenter: presenter) - - presenter.view = view - interactor.presenter = presenter - - return view - } - - private static func createInteractor() -> WalletConnectInteractor { - let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) - let service = WalletConnectService(metadata: metadata) - - let mediator = DAppInteractionFactory.createMediator() - mediator.setup() - - let dataSource = DAppStateDataSource( - chainsStore: mediator.chainsStore, - dAppSettingsRepository: mediator.settingsRepository, - walletSettings: SelectedWalletSettings.shared, - operationQueue: mediator.operationQueue - ) - - let transport = WalletConnectTransport( - service: service, - dataSource: dataSource, - logger: Logger.shared - ) - - return .init(mediator: mediator, transport: transport) - } -} diff --git a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift deleted file mode 100644 index 76a0e941bd..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/WalletConnectViewLayout.swift +++ /dev/null @@ -1,21 +0,0 @@ -import UIKit - -final class WalletConnectViewLayout: UIView { - let scanItem = UIBarButtonItem( - image: R.image.iconScanQr()!, - style: .plain, - target: nil, - action: nil - ) - - override init(frame: CGRect) { - super.init(frame: frame) - - backgroundColor = R.color.colorSecondaryScreenBackground()! - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift b/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift index 6495fbc3b5..9fd803bd19 100644 --- a/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift +++ b/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift @@ -45,7 +45,10 @@ final class MainTabBarViewFactory: MainTabBarViewFactoryProtocol { return nil } - guard let settingsController = createProfileController(for: localizationManager) else { + guard let settingsController = createProfileController( + for: localizationManager, + dappMediator: serviceCoordinator.dappMediator + ) else { return nil } @@ -144,9 +147,12 @@ final class MainTabBarViewFactory: MainTabBarViewFactoryProtocol { } static func createProfileController( - for localizationManager: LocalizationManagerProtocol + for localizationManager: LocalizationManagerProtocol, + dappMediator: DAppInteractionMediating ) -> UIViewController? { - guard let viewController = SettingsViewFactory.createView()?.controller else { return nil } + guard let view = SettingsViewFactory.createView(with: dappMediator) else { return nil } + + let viewController = view.controller let navigationController = NovaNavigationController(rootViewController: viewController) diff --git a/novawallet/Modules/Settings/Model/SettingsParameters.swift b/novawallet/Modules/Settings/Model/SettingsParameters.swift new file mode 100644 index 0000000000..8906cecbf1 --- /dev/null +++ b/novawallet/Modules/Settings/Model/SettingsParameters.swift @@ -0,0 +1,5 @@ +import Foundation + +struct SettingsParameters { + let walletConnectSessionsCount: Int? +} diff --git a/novawallet/Modules/Settings/SettingsInteractor.swift b/novawallet/Modules/Settings/SettingsInteractor.swift index 80234cf6d9..1249ce9bf5 100644 --- a/novawallet/Modules/Settings/SettingsInteractor.swift +++ b/novawallet/Modules/Settings/SettingsInteractor.swift @@ -11,14 +11,17 @@ final class SettingsInteractor { let selectedWalletSettings: SelectedWalletSettings let eventCenter: EventCenterProtocol + let walletConnect: WalletConnectDelegateInputProtocol init( selectedWalletSettings: SelectedWalletSettings, eventCenter: EventCenterProtocol, + walletConnect: WalletConnectDelegateInputProtocol, currencyManager: CurrencyManagerProtocol ) { self.selectedWalletSettings = selectedWalletSettings self.eventCenter = eventCenter + self.walletConnect = walletConnect self.currencyManager = currencyManager } @@ -32,14 +35,27 @@ final class SettingsInteractor { presenter?.didReceiveUserDataProvider(error: error) } } + + private func provideWalletConnectSessionsCount() { + let count = walletConnect.getSessionsCount() + + presenter?.didReceiveWalletConnect(sessionsCount: count) + } } extension SettingsInteractor: SettingsInteractorInputProtocol { func setup() { eventCenter.add(observer: self, dispatchIn: .main) + walletConnect.add(delegate: self) + provideUserSettings() + provideWalletConnectSessionsCount() applyCurrency() } + + func connectWalletConnect(uri: String) { + walletConnect.connect(uri: uri) + } } extension SettingsInteractor: EventVisitorProtocol { @@ -52,6 +68,12 @@ extension SettingsInteractor: EventVisitorProtocol { } } +extension SettingsInteractor: WalletConnectDelegateOutputProtocol { + func walletConnectDidChangeSessions() { + provideWalletConnectSessionsCount() + } +} + extension SettingsInteractor: SelectedCurrencyDepending { func applyCurrency() { guard let presenter = presenter, diff --git a/novawallet/Modules/Settings/SettingsPresenter.swift b/novawallet/Modules/Settings/SettingsPresenter.swift index ce41178b73..547d315b23 100644 --- a/novawallet/Modules/Settings/SettingsPresenter.swift +++ b/novawallet/Modules/Settings/SettingsPresenter.swift @@ -11,6 +11,7 @@ final class SettingsPresenter { private var currency: String? private var wallet: MetaAccountModel? + private var walletConnectSessionsCount: Int? init( viewModelFactory: SettingsViewModelFactoryProtocol, @@ -34,6 +35,7 @@ final class SettingsPresenter { let sectionViewModels = viewModelFactory.createSectionViewModels( language: localizationManager?.selectedLanguage, currency: currency, + parameters: .init(walletConnectSessionsCount: walletConnectSessionsCount), locale: locale ) view?.reload(sections: sectionViewModels) @@ -118,7 +120,11 @@ extension SettingsPresenter: SettingsPresenterProtocol { case .privacyPolicy: show(url: config.privacyPolicyURL) case .walletConnect: - wireframe.showWalletConnect(from: view) + if let count = walletConnectSessionsCount, count > 0 { + wireframe.showWalletConnect(from: view) + } else { + wireframe.showScan(from: view, delegate: self) + } } } @@ -155,6 +161,20 @@ extension SettingsPresenter: SettingsInteractorOutputProtocol { updateView() } } + + func didReceiveWalletConnect(sessionsCount: Int) { + walletConnectSessionsCount = sessionsCount + + updateView() + } +} + +extension SettingsPresenter: URIScanDelegate { + func uriScanDidReceive(uri: String, context _: AnyObject?) { + wireframe.hideUriScanAnimated(from: view) { [weak self] in + self?.interactor.connectWalletConnect(uri: uri) + } + } } extension SettingsPresenter: Localizable { diff --git a/novawallet/Modules/Settings/SettingsProtocols.swift b/novawallet/Modules/Settings/SettingsProtocols.swift index a1a9f95686..abb7ae44da 100644 --- a/novawallet/Modules/Settings/SettingsProtocols.swift +++ b/novawallet/Modules/Settings/SettingsProtocols.swift @@ -14,28 +14,20 @@ protocol SettingsPresenterProtocol: AnyObject { func handleSwitchAction() } -protocol SettingsViewModelFactoryProtocol: AnyObject { - func createAccountViewModel(for wallet: MetaAccountModel) -> SettingsAccountViewModel - - func createSectionViewModels( - language: Language?, - currency: String?, - locale: Locale - ) -> [(SettingsSection, [SettingsCellViewModel])] -} - protocol SettingsInteractorInputProtocol: AnyObject { func setup() + func connectWalletConnect(uri: String) } protocol SettingsInteractorOutputProtocol: AnyObject { func didReceive(wallet: MetaAccountModel) func didReceiveUserDataProvider(error: Error) func didReceive(currencyCode: String) + func didReceiveWalletConnect(sessionsCount: Int) } protocol SettingsWireframeProtocol: ErrorPresentable, AlertPresentable, WebPresentable, ModalAlertPresenting, - EmailPresentable, WalletSwitchPresentable { + EmailPresentable, WalletSwitchPresentable, WalletConnectScanPresentable { func showAccountDetails(for walletId: String, from view: ControllerBackedProtocol?) func showAccountSelection(from view: ControllerBackedProtocol?) func showLanguageSelection(from view: ControllerBackedProtocol?) diff --git a/novawallet/Modules/Settings/SettingsViewFactory.swift b/novawallet/Modules/Settings/SettingsViewFactory.swift index d57cb4a79f..0606024a74 100644 --- a/novawallet/Modules/Settings/SettingsViewFactory.swift +++ b/novawallet/Modules/Settings/SettingsViewFactory.swift @@ -5,21 +5,30 @@ import IrohaCrypto import SubstrateSdk struct SettingsViewFactory { - static func createView() -> SettingsViewProtocol? { - guard let currencyManager = CurrencyManager.shared else { + static func createView(with dappMediator: DAppInteractionMediating) -> SettingsViewProtocol? { + guard + let currencyManager = CurrencyManager.shared, + let walletConnect = dappMediator.children.first( + where: { $0 is WalletConnectDelegateInputProtocol } + ) as? WalletConnectDelegateInputProtocol else { return nil } + let localizationManager = LocalizationManager.shared - let profileViewModelFactory = SettingsViewModelFactory(iconGenerator: NovaIconGenerator()) + let profileViewModelFactory = SettingsViewModelFactory( + iconGenerator: NovaIconGenerator(), + quantityFormatter: NumberFormatter.quantity.localizableResource() + ) let interactor = SettingsInteractor( selectedWalletSettings: SelectedWalletSettings.shared, eventCenter: EventCenter.shared, + walletConnect: walletConnect, currencyManager: currencyManager ) - let wireframe = SettingsWireframe() + let wireframe = SettingsWireframe(dappMediator: dappMediator) let view = SettingsViewController() diff --git a/novawallet/Modules/Settings/SettingsWireframe.swift b/novawallet/Modules/Settings/SettingsWireframe.swift index 8841ce42f6..b5a0cd1180 100644 --- a/novawallet/Modules/Settings/SettingsWireframe.swift +++ b/novawallet/Modules/Settings/SettingsWireframe.swift @@ -2,6 +2,12 @@ import Foundation import UIKit final class SettingsWireframe: SettingsWireframeProtocol, AuthorizationPresentable { + let dappMediator: DAppInteractionMediating + + init(dappMediator: DAppInteractionMediating) { + self.dappMediator = dappMediator + } + func showAccountDetails(for walletId: String, from view: ControllerBackedProtocol?) { guard let accountManagement = AccountManagementViewFactory.createView(for: walletId) else { return @@ -70,7 +76,10 @@ final class SettingsWireframe: SettingsWireframeProtocol, AuthorizationPresentab } func showWalletConnect(from view: ControllerBackedProtocol?) { - guard let walletConnectView = WalletConnectViewFactory.createView() else { + guard + let walletConnectView = WalletConnectSessionsViewFactory.createView( + with: dappMediator + ) else { return } diff --git a/novawallet/Modules/Settings/ViewModel/SettingsRow.swift b/novawallet/Modules/Settings/ViewModel/SettingsRow.swift index 734b2c2133..17fb8253b1 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsRow.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsRow.swift @@ -82,7 +82,7 @@ extension SettingsRow { case .privacyPolicy: return R.image.iconTerms()! case .walletConnect: - return R.image.iconWallets() + return R.image.iconWalletConnect()! } } } diff --git a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift index 0624c1751d..0836e0c7e1 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift @@ -3,11 +3,27 @@ import SoraFoundation import SubstrateSdk import IrohaCrypto +protocol SettingsViewModelFactoryProtocol: AnyObject { + func createAccountViewModel(for wallet: MetaAccountModel) -> SettingsAccountViewModel + + func createSectionViewModels( + language: Language?, + currency: String?, + parameters: SettingsParameters, + locale: Locale + ) -> [(SettingsSection, [SettingsCellViewModel])] +} + final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { let iconGenerator: IconGenerating + let quantityFormatter: LocalizableResource - init(iconGenerator: IconGenerating) { + init( + iconGenerator: IconGenerating, + quantityFormatter: LocalizableResource + ) { self.iconGenerator = iconGenerator + self.quantityFormatter = quantityFormatter } func createAccountViewModel(for wallet: MetaAccountModel) -> SettingsAccountViewModel { @@ -28,12 +44,13 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { func createSectionViewModels( language: Language?, currency: String?, + parameters: SettingsParameters, locale: Locale ) -> [(SettingsSection, [SettingsCellViewModel])] { [ (.general, [ createCommonViewViewModel(row: .wallets, locale: locale), - createCommonViewViewModel(row: .walletConnect, locale: locale) + createWalletConnectViewModel(from: parameters.walletConnectSessionsCount, locale: locale) ]), (.preferences, [ createValuableViewModel(row: .currency, value: currency, locale: locale), @@ -84,6 +101,28 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { return viewModel } + private func createWalletConnectViewModel( + from counter: Int?, + locale: Locale + ) -> SettingsCellViewModel { + let row = SettingsRow.walletConnect + + let subtitle: String? = counter.flatMap { counter in + if counter > 0 { + return quantityFormatter.value(for: locale).string(from: .init(value: counter)) + } else { + return nil + } + } + + return SettingsCellViewModel( + row: row, + title: row.title(for: locale), + icon: row.icon, + accessoryTitle: subtitle + ) + } + private func createValuableViewModel( row: SettingsRow, value: String?, diff --git a/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift b/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift new file mode 100644 index 0000000000..2b34ff2ce3 --- /dev/null +++ b/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift @@ -0,0 +1,16 @@ +import XCTest + +class WalletConnectSessionsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + XCTFail("Did you forget to add tests?") + } +} From 670a410d41aed28517f410b9877cbcd088d9bcd9 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 3 May 2023 18:02:25 +0500 Subject: [PATCH 22/54] save wallet connect session --- novawallet.xcodeproj/project.pbxproj | 18 +--- .../iconButtonScan.imageset/Contents.json | 12 +++ .../iconButtonScan.pdf | Bin 0 -> 3430 bytes .../DAppLocalSubscriptionFactory.swift | 2 +- .../Foundation/NSPredicate+Filter.swift | 13 ++- .../Helpers/AccountRepositoryFactory.swift | 2 +- .../Common/Migration/UserStorageVersion.swift | 3 + .../.xccurrentversion | 2 +- .../contents | 45 ++++++++++ .../Storage/UserDataStorageFacade.swift | 2 +- .../DAppMetamaskAuthorizingState.swift | 6 +- .../DAppBrowserAuthorizingState.swift | 6 +- .../DAppInteractionFactory.swift | 5 ++ .../DAppStateDataSource.swift | 3 + .../Modules/DApp/Model/DAppSettings.swift | 1 + .../DApp/Model/DAppSettingsMapper.swift | 3 +- .../Service/WalletConnectServiceFactory.swift | 2 + .../WalletConnectSessionsViewController.swift | 30 ++++++- .../WalletConnectSessionsViewLayout.swift | 39 ++++++++ .../WalletConnectStateAuthorizing.swift | 85 +++++++++++++++--- .../States/WalletConnectStateNewMessage.swift | 54 ++++++++++- novawallet/en.lproj/Localizable.strings | 7 +- novawallet/ru.lproj/Localizable.strings | 6 +- .../WalletConnectSessionsTests.swift | 16 ---- 24 files changed, 299 insertions(+), 63 deletions(-) create mode 100644 novawallet/Assets.xcassets/iconButtonScan.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconButtonScan.imageset/iconButtonScan.pdf create mode 100644 novawallet/Common/Storage/UserDataModel.xcdatamodeld/MultiassetUserDataModel8.xcdatamodel/contents rename novawallet/Modules/DApp/{WalletConnect/Model => DAppInteraction}/DAppStateDataSource.swift (78%) delete mode 100644 novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index fc7958ef5a..91f0e77e98 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -307,7 +307,6 @@ 61B9688494251703A6373A1B /* StakingAmountWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6216F6F1B91F798F07695FB6 /* StakingAmountWireframe.swift */; }; 61E0DC83C1D60D677274D7CE /* AccountExportPasswordViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11575D8B4F64C2E805372A5 /* AccountExportPasswordViewFactory.swift */; }; 623474C49445578F030291B0 /* ParaStkStakeSetupWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B2C32E11E2F7F3D4A1D3AB /* ParaStkStakeSetupWireframe.swift */; }; - 6252DBD2E52DB3F9459E9911 /* WalletConnectSessionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */; }; 62649D3FB6AACB508872C67A /* GovernanceUnlockConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9638E6EDBA41A5772E0033AE /* GovernanceUnlockConfirmInteractor.swift */; }; 62B2298F132DB0CE0794DD7A /* MarkdownDescriptionWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E6825E525785A1A12C62E7 /* MarkdownDescriptionWireframe.swift */; }; 63185C6D67EAEB2867069AB9 /* ParitySignerWelcomeProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBB8745F8C36BB107625E8F /* ParitySignerWelcomeProtocols.swift */; }; @@ -3632,7 +3631,6 @@ 3B1D64215349613366E49F8F /* DelegationListInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationListInteractor.swift; sourceTree = ""; }; 3B5E099C1E3DC3730DD503BE /* ParaStkYourCollatorsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYourCollatorsViewController.swift; sourceTree = ""; }; 3B6244A9538B39AFCD3A6F3A /* SelectValidatorsStartPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsStartPresenter.swift; sourceTree = ""; }; - 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsTests.swift; sourceTree = ""; }; 3B8473AA386E1AD6F0F0C964 /* ControllerAccountConfirmationInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountConfirmationInteractor.swift; sourceTree = ""; }; 3BB7BFB2477E9F3893126931 /* DAppOperationConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppOperationConfirmViewFactory.swift; sourceTree = ""; }; 3BEEFB03BBA45F5143484398 /* ParaStkRedeemViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkRedeemViewFactory.swift; sourceTree = ""; }; @@ -5593,6 +5591,7 @@ 84C9CF3A291AE328002BF328 /* GovernanceV1PolkassemblyOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceV1PolkassemblyOperationFactory.swift; sourceTree = ""; }; 84C9CF3C291AF1B1002BF328 /* GovernanceOffchainApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainApi.swift; sourceTree = ""; }; 84C9CF3E291AF4B2002BF328 /* ReferendumMetadataDetailsProviderSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataDetailsProviderSource.swift; sourceTree = ""; }; + 84CA40732A0274C2004BB71E /* MultiassetUserDataModel8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MultiassetUserDataModel8.xcdatamodel; sourceTree = ""; }; 84CA68CE26BD6872003B9453 /* RuntimeSyncService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeSyncService.swift; sourceTree = ""; }; 84CA68D026BE99ED003B9453 /* RuntimeProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeProviderFactory.swift; sourceTree = ""; }; 84CA68D226BE9A35003B9453 /* RuntimeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeProvider.swift; sourceTree = ""; }; @@ -11893,7 +11892,6 @@ 849F33B529F7B225001AEFA4 /* Model */ = { isa = PBXGroup; children = ( - 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */, 84B8AA8629F9115400347A37 /* WalletConnectTransportMessage.swift */, 840EAE6829FA935900453C7E /* WalletConnectModelFactory.swift */, 840EAE6A29FB8AEA00453C7E /* WalletConnectMethod.swift */, @@ -11913,6 +11911,7 @@ 84B8AA7029F8E59300347A37 /* DAppInteractionPresenter.swift */, 84B8AA7229F8EFC700347A37 /* DAppInteractionError.swift */, 84B8AA7429F8FD2400347A37 /* DAppInteractionFactory.swift */, + 849F33B629F7B270001AEFA4 /* DAppStateDataSource.swift */, ); path = DAppInteraction; sourceTree = ""; @@ -12256,7 +12255,6 @@ 84B7C705289BFA79001A3566 /* AccountManagement */, 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, - FEEA0BAB955E266213341299 /* WalletConnectSessions */, ); path = Modules; sourceTree = ""; @@ -16380,14 +16378,6 @@ path = GovernanceUnlockSetup; sourceTree = ""; }; - FEEA0BAB955E266213341299 /* WalletConnectSessions */ = { - isa = PBXGroup; - children = ( - 3B6C77973D33DE26F59D5DE0 /* WalletConnectSessionsTests.swift */, - ); - path = WalletConnectSessions; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -20175,7 +20165,6 @@ 84B7C748289BFA79001A3566 /* WalletListTests.swift in Sources */, 84B7C720289BFA79001A3566 /* ReferralCrowdloanTests.swift in Sources */, F4897BB126AED13D0075F291 /* EraCountdownOperationFactoryStub.swift in Sources */, - 6252DBD2E52DB3F9459E9911 /* WalletConnectSessionsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -20866,6 +20855,7 @@ 8467FD4F24EFD11E005D486C /* UserDataModel.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 84CA40732A0274C2004BB71E /* MultiassetUserDataModel8.xcdatamodel */, 8881572D295AE5C600912F69 /* MultiassetUserDataModel7.xcdatamodel */, 84466B3128B652BA00FA1E0D /* MultiassetUserDataModel6.xcdatamodel */, 8450805128868B6E00FB3CB6 /* MultiassetUserDataModel5.xcdatamodel */, @@ -20875,7 +20865,7 @@ 848DE75726D595660045CD29 /* MultiassetUserDataModel.xcdatamodel */, 8467FD5024EFD11E005D486C /* UserDataModel.xcdatamodel */, ); - currentVersion = 8881572D295AE5C600912F69 /* MultiassetUserDataModel7.xcdatamodel */; + currentVersion = 84CA40732A0274C2004BB71E /* MultiassetUserDataModel8.xcdatamodel */; path = UserDataModel.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/novawallet/Assets.xcassets/iconButtonScan.imageset/Contents.json b/novawallet/Assets.xcassets/iconButtonScan.imageset/Contents.json new file mode 100644 index 0000000000..a53c57d0ae --- /dev/null +++ b/novawallet/Assets.xcassets/iconButtonScan.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iconButtonScan.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconButtonScan.imageset/iconButtonScan.pdf b/novawallet/Assets.xcassets/iconButtonScan.imageset/iconButtonScan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..340c8e72d9c50ba37dd6bc62b87b83a10824ae17 GIT binary patch literal 3430 zcmZWs+in{-5Pj!Y@Wnt20xUq#wV){#R%V#!T?6F@lhwl5c>pe=?y0$8kDt`AI4zaa>*zdwX8GRsRJ=c zA!;ycieVj%;tNHo#t!7>a6(=X)f{97TP}if@MoB1HYd%iM2W0AsF`$&T>C^?C^LA< z3F&Fpi;6z@h=hVl6_(NnPA>bg0yWO{7Ggu7icp7EY%OA3b$!4X?Bm5?3ynrD%>Sa* ziL`}K1u(d%IjeNZURQDqmCVvSpXcr)l7&R!Bm`K2eqPhEEM^`ypnL>b7mS+(L|u zf-;b(W*MV2p}Z21syjybJhH2+*wbCZs_1arE0>M;s7lrxlol;jaVI$ygUu$>@>y}% z0D*_TSu;3hzHt~C1<@&SU~DIdbi$#{5{{u#a96R|7y*anLhP_AHQtHE!sjtE3g*Sw z4j3HFI-%`UjY2dIw-+^{sxoK`k{JRJ78!jEF=${_W3)l>kR{e3wKO|W>)+61|bD!xUZZi#Jew}dPZ*7}RxvjghbWxVvhG*@f6LP<1@zNDO zj|D9k40ZBt>pJ~5ut}3gj|PECoopceHn`L##2RiIU=cT{Q*i@HLKx(zKcWNZWn6dy z=x||pc+4QzA0XU+sN7b5nYvsWA}�hET;}K$GOXlFD;u<0Bct4Vo4n43-w>OdbX& z8AQk;(KV6}=pp9Oz{VF?-LARS&TSZZ@ggV`tIywwz4%qos9WdQW$2~(b{?8-95T7c~2hu$61}k)z$4UAN~V let mapper = DAppSettingsMapper() - let filter = NSPredicate.filterAuthorizedDApps(by: metaId) + let filter = NSPredicate.filterAuthorizedBrowserDApps(by: metaId) repository = storageFacade.createRepository( filter: filter, sortDescriptors: [], diff --git a/novawallet/Common/Extension/Foundation/NSPredicate+Filter.swift b/novawallet/Common/Extension/Foundation/NSPredicate+Filter.swift index 3532f4dcc5..fc1a8ec993 100644 --- a/novawallet/Common/Extension/Foundation/NSPredicate+Filter.swift +++ b/novawallet/Common/Extension/Foundation/NSPredicate+Filter.swift @@ -359,8 +359,17 @@ extension NSPredicate { NSPredicate(format: "%K == %@", #keyPath(CDDAppFavorite.identifier), identifier) } - static func filterAuthorizedDApps(by metaId: String) -> NSPredicate { - NSPredicate(format: "%K == %@", #keyPath(CDDAppSettings.metaId), metaId) + static func filterAuthorizedBrowserDApps(by metaId: String) -> NSPredicate { + let metaId = NSPredicate(format: "%K == %@", #keyPath(CDDAppSettings.metaId), metaId) + let source = NSPredicate(format: "%K = nil", #keyPath(CDDAppSettings.source)) + + return NSCompoundPredicate(andPredicateWithSubpredicates: [metaId, source]) + } + + static func filterWalletConnectSessions() -> NSPredicate { + NSPredicate( + format: "%K == %@", #keyPath(CDDAppSettings.source), DAppTransports.walletConnect + ) } static func crowdloanContribution(chainIds: Set) -> NSPredicate { diff --git a/novawallet/Common/Helpers/AccountRepositoryFactory.swift b/novawallet/Common/Helpers/AccountRepositoryFactory.swift index dc526e2c23..af4c8a01eb 100644 --- a/novawallet/Common/Helpers/AccountRepositoryFactory.swift +++ b/novawallet/Common/Helpers/AccountRepositoryFactory.swift @@ -88,7 +88,7 @@ final class AccountRepositoryFactory: AccountRepositoryFactoryProtocol { func createAuthorizedDAppsRepository(for metaId: String?) -> AnyDataProviderRepository { let mapper = DAppSettingsMapper() - let filter = metaId.map { NSPredicate.filterAuthorizedDApps(by: $0) } + let filter = metaId.map { NSPredicate.filterAuthorizedBrowserDApps(by: $0) } let repository = storageFacade.createRepository( filter: filter, sortDescriptors: [], diff --git a/novawallet/Common/Migration/UserStorageVersion.swift b/novawallet/Common/Migration/UserStorageVersion.swift index 130c14a3a1..bc8015c99b 100644 --- a/novawallet/Common/Migration/UserStorageVersion.swift +++ b/novawallet/Common/Migration/UserStorageVersion.swift @@ -9,6 +9,7 @@ enum UserStorageVersion: String, CaseIterable { case version6 = "MultiassetUserDataModel5" case version7 = "MultiassetUserDataModel6" case version8 = "MultiassetUserDataModel7" + case version9 = "MultiassetUserDataModel8" static var current: UserStorageVersion { guard let currentVersion = allCases.last else { @@ -35,6 +36,8 @@ enum UserStorageVersion: String, CaseIterable { case .version7: return .version8 case .version8: + return .version9 + case .version9: return nil } } diff --git a/novawallet/Common/Storage/UserDataModel.xcdatamodeld/.xccurrentversion b/novawallet/Common/Storage/UserDataModel.xcdatamodeld/.xccurrentversion index 9484645c55..54ec87cc5b 100644 --- a/novawallet/Common/Storage/UserDataModel.xcdatamodeld/.xccurrentversion +++ b/novawallet/Common/Storage/UserDataModel.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MultiassetUserDataModel7.xcdatamodel + MultiassetUserDataModel8.xcdatamodel diff --git a/novawallet/Common/Storage/UserDataModel.xcdatamodeld/MultiassetUserDataModel8.xcdatamodel/contents b/novawallet/Common/Storage/UserDataModel.xcdatamodeld/MultiassetUserDataModel8.xcdatamodel/contents new file mode 100644 index 0000000000..679911da93 --- /dev/null +++ b/novawallet/Common/Storage/UserDataModel.xcdatamodeld/MultiassetUserDataModel8.xcdatamodel/contents @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/novawallet/Common/Storage/UserDataStorageFacade.swift b/novawallet/Common/Storage/UserDataStorageFacade.swift index dc25613869..6e2dffe0d6 100644 --- a/novawallet/Common/Storage/UserDataStorageFacade.swift +++ b/novawallet/Common/Storage/UserDataStorageFacade.swift @@ -16,7 +16,7 @@ enum UserStorageParams { * - update mappings between CoreData Entities and App Models; * - switch version of UserStorageParams.modelVersion; */ - static let modelVersion: UserStorageVersion = .version8 + static let modelVersion: UserStorageVersion = .version9 static let modelDirectory: String = "UserDataModel.momd" static let databaseName = "UserDataModel.sqlite" diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskAuthorizingState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskAuthorizingState.swift index d7db05d4a6..519cd76f67 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskAuthorizingState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskAuthorizingState.swift @@ -28,7 +28,11 @@ final class DAppMetamaskAuthorizingState: DAppMetamaskBaseState { } let saveOperation = dataSource.dAppSettingsRepository.saveOperation({ - let newSettings = DAppSettings(identifier: host, metaId: dataSource.wallet.metaId) + let newSettings = DAppSettings( + identifier: host, + metaId: dataSource.wallet.metaId, + source: nil + ) return [newSettings] }, { [] }) diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizingState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizingState.swift index c7525e3a1e..6ef4f58d9d 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizingState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserAuthorizingState.swift @@ -17,7 +17,11 @@ final class DAppBrowserAuthorizingState: DAppBrowserBaseState { } let saveOperation = dataSource.dAppSettingsRepository.saveOperation({ - let newSettings = DAppSettings(identifier: identifier, metaId: dataSource.wallet.metaId) + let newSettings = DAppSettings( + identifier: identifier, + metaId: dataSource.wallet.metaId, + source: nil + ) return [newSettings] }, { [] }) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift index 3466302020..a92e075691 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift @@ -9,6 +9,10 @@ final class DAppInteractionFactory { let storageFacade = UserDataStorageFacade.shared let accountRepositoryFactory = AccountRepositoryFactory(storageFacade: storageFacade) let settingsRepository = accountRepositoryFactory.createAuthorizedDAppsRepository(for: nil) + let walletsRepository = accountRepositoryFactory.createMetaAccountRepository( + for: nil, + sortDescriptors: [] + ) let phishingVerifier = PhishingSiteVerifier.createSequentialVerifier() @@ -17,6 +21,7 @@ final class DAppInteractionFactory { let walletConnect = WalletConnectServiceFactory.createInteractor( chainsStore: chainsStore, settingsRepository: settingsRepository, + walletsRepository: walletsRepository, operationQueue: OperationManagerFacade.sharedDefaultQueue ) diff --git a/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift b/novawallet/Modules/DApp/DAppInteraction/DAppStateDataSource.swift similarity index 78% rename from novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift rename to novawallet/Modules/DApp/DAppInteraction/DAppStateDataSource.swift index c5879f2088..ae83a3ec0a 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/DAppStateDataSource.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppStateDataSource.swift @@ -4,17 +4,20 @@ import RobinHood final class DAppStateDataSource { let chainsStore: ChainsStoreProtocol let dAppSettingsRepository: AnyDataProviderRepository + let walletsRepository: AnyDataProviderRepository let operationQueue: OperationQueue let walletSettings: SelectedWalletSettings init( chainsStore: ChainsStoreProtocol, dAppSettingsRepository: AnyDataProviderRepository, + walletsRepository: AnyDataProviderRepository, walletSettings: SelectedWalletSettings, operationQueue: OperationQueue ) { self.chainsStore = chainsStore self.dAppSettingsRepository = dAppSettingsRepository + self.walletsRepository = walletsRepository self.walletSettings = walletSettings self.operationQueue = operationQueue } diff --git a/novawallet/Modules/DApp/Model/DAppSettings.swift b/novawallet/Modules/DApp/Model/DAppSettings.swift index 6390b7a352..4b9a87f250 100644 --- a/novawallet/Modules/DApp/Model/DAppSettings.swift +++ b/novawallet/Modules/DApp/Model/DAppSettings.swift @@ -5,4 +5,5 @@ struct DAppSettings: Identifiable { // normaly it is a dapp url's host let identifier: String let metaId: String? + let source: String? } diff --git a/novawallet/Modules/DApp/Model/DAppSettingsMapper.swift b/novawallet/Modules/DApp/Model/DAppSettingsMapper.swift index b96cfa6980..b21e2e2597 100644 --- a/novawallet/Modules/DApp/Model/DAppSettingsMapper.swift +++ b/novawallet/Modules/DApp/Model/DAppSettingsMapper.swift @@ -7,7 +7,7 @@ final class DAppSettingsMapper: CoreDataMapperProtocol { typealias CoreDataEntity = CDDAppSettings func transform(entity: CoreDataEntity) throws -> DataProviderModel { - DAppSettings(identifier: entity.identifier!, metaId: entity.metaId) + DAppSettings(identifier: entity.identifier!, metaId: entity.metaId, source: entity.source) } func populate( @@ -17,6 +17,7 @@ final class DAppSettingsMapper: CoreDataMapperProtocol { ) throws { entity.identifier = model.identifier entity.metaId = model.metaId + entity.source = model.source } var entityIdentifierFieldName: String { #keyPath(CDDAppSettings.identifier) } diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift index e6c906251b..6907ba7714 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift @@ -5,6 +5,7 @@ struct WalletConnectServiceFactory { static func createInteractor( chainsStore: ChainsStoreProtocol, settingsRepository: AnyDataProviderRepository, + walletsRepository: AnyDataProviderRepository, operationQueue: OperationQueue ) -> WalletConnectInteractor { let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) @@ -13,6 +14,7 @@ struct WalletConnectServiceFactory { let dataSource = DAppStateDataSource( chainsStore: chainsStore, dAppSettingsRepository: settingsRepository, + walletsRepository: walletsRepository, walletSettings: SelectedWalletSettings.shared, operationQueue: operationQueue ) diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift index 4ba17be952..b39a9d4165 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift @@ -1,6 +1,7 @@ import UIKit +import SoraFoundation -final class WalletConnectSessionsViewController: UIViewController { +final class WalletConnectSessionsViewController: UIViewController, ViewHolder { typealias RootViewType = WalletConnectSessionsViewLayout let presenter: WalletConnectSessionsPresenterProtocol @@ -23,14 +24,29 @@ final class WalletConnectSessionsViewController: UIViewController { super.viewDidLoad() setupLocalization() + setupHandlers() presenter.setup() } - private func setupHandlers() {} + private func setupHandlers() { + rootView.scanButton.addTarget( + self, + action: #selector(actionScan), + for: .touchUpInside + ) + } private func setupLocalization() { - title = "Your sessions" + title = R.string.localizable.commonWalletConnect( + preferredLanguages: selectedLocale.rLanguages + ) + + rootView.scanButton.imageWithTitleView?.title = R.string.localizable.walletConnectScanButton( + preferredLanguages: selectedLocale.rLanguages + ) + + rootView.scanButton.invalidateLayout() } @objc func actionScan() { @@ -39,3 +55,11 @@ final class WalletConnectSessionsViewController: UIViewController { } extension WalletConnectSessionsViewController: WalletConnectSessionsViewProtocol {} + +extension WalletConnectSessionsViewController: Localizable { + func applyLocalization() { + if isViewLoaded { + setupLocalization() + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift index dc9c9795ef..037bbfb1ca 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift @@ -1,12 +1,51 @@ import UIKit +import SoraUI final class WalletConnectSessionsViewLayout: UIView { + let scanButton: TriangularedButton = { + let button = TriangularedButton() + button.applyDefaultStyle() + button.changesContentOpacityWhenHighlighted = true + button.imageWithTitleView?.iconImage = R.image.iconButtonScan() + button.imageWithTitleView?.spacingBetweenLabelAndIcon = 8 + return button + }() + + let tableView: UITableView = { + let view = UITableView() + view.separatorStyle = .none + view.backgroundColor = .clear + return view + }() + override init(frame: CGRect) { super.init(frame: frame) + + backgroundColor = R.color.colorSecondaryScreenBackground() + + setupLayout() } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupLayout() { + addSubview(tableView) + tableView.snp.makeConstraints { make in + make.top.equalTo(safeAreaLayoutGuide.snp.top) + make.leading.trailing.bottom.equalToSuperview() + } + + addSubview(scanButton) + scanButton.snp.makeConstraints { make in + make.height.equalTo(UIConstants.actionHeight) + make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) + make.bottom.equalTo(safeAreaLayoutGuide).inset(UIConstants.actionBottomInset) + } + + let bottomInset = UIConstants.actionBottomInset + UIConstants.actionHeight + 16.0 + tableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0) + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift index 138b0d0dad..3044237181 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift @@ -15,6 +15,47 @@ class WalletConnectStateAuthorizing: WalletConnectBaseState { super.init(stateMachine: stateMachine) } + + private func save( + authResponse: DAppAuthResponse, + pairingId: String, + dataSource: DAppStateDataSource, + completion: @escaping (Error?) -> Void + ) { + let saveOperation = dataSource.dAppSettingsRepository.saveOperation({ + if authResponse.approved { + let settings = DAppSettings( + identifier: pairingId, + metaId: authResponse.wallet.metaId, + source: DAppTransports.walletConnect + ) + + return [settings] + } else { + return [] + } + }, { + if !authResponse.approved { + return [pairingId] + } else { + return [] + } + }) + + saveOperation.completionBlock = { + DispatchQueue.main.async { + do { + try saveOperation.extractNoCancellableResultData() + + completion(nil) + } catch { + completion(error) + } + } + } + + dataSource.operationQueue.addOperation(saveOperation) + } } extension WalletConnectStateAuthorizing: WalletConnectStateProtocol { @@ -30,28 +71,44 @@ extension WalletConnectStateAuthorizing: WalletConnectStateProtocol { emitUnexpected(message: response, nextState: self) } - func handleAuth(response: DAppAuthResponse, dataSource _: DAppStateDataSource) { + func handleAuth(response: DAppAuthResponse, dataSource: DAppStateDataSource) { guard let stateMachine = stateMachine else { return } let nextState = WalletConnectStateReady(stateMachine: stateMachine) - guard response.approved else { - stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) - return - } + save( + authResponse: response, + pairingId: proposal.pairingTopic, + dataSource: dataSource + ) { [weak self] optError in + guard let proposal = self?.proposal, let resolution = self?.resolution else { + return + } - let namespaces = WalletConnectModelFactory.createSessionNamespaces( - from: proposal, - wallet: response.wallet, - resolvedChains: resolution.allResolvedChains().resolved - ) + guard optError == nil else { + // TODO: Also notify error + stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) + return + } - stateMachine.emit( - proposalDecision: .approve(proposal: proposal, namespaces: namespaces), - nextState: nextState - ) + guard response.approved else { + stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) + return + } + + let namespaces = WalletConnectModelFactory.createSessionNamespaces( + from: proposal, + wallet: response.wallet, + resolvedChains: resolution.allResolvedChains().resolved + ) + + stateMachine.emit( + proposalDecision: .approve(proposal: proposal, namespaces: namespaces), + nextState: nextState + ) + } } func proceed(with _: DAppStateDataSource) {} diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index ebeae4763f..7df042533c 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -81,6 +81,46 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { ) } + private func fetchWallet( + for session: Session, + dataSource: DAppStateDataSource, + completion: @escaping (MetaAccountModel?) -> Void + ) { + let settingsOperation = dataSource.dAppSettingsRepository.fetchOperation( + by: { session.pairingTopic }, + options: .init() + ) + + let walletOperation = dataSource.walletsRepository.fetchOperation( + by: { + if let metaId = try settingsOperation.extractNoCancellableResultData()?.metaId { + return metaId + } else { + throw ChainAccountFetchingError.accountNotExists + } + + }, + options: .init() + ) + + walletOperation.addDependency(settingsOperation) + + walletOperation.completionBlock = { + DispatchQueue.main.async { + do { + let wallet = try walletOperation.extractNoCancellableResultData() + completion(wallet) + } catch { + completion(nil) + } + } + } + + let operations = [settingsOperation, walletOperation] + + dataSource.operationQueue.addOperations(operations, waitUntilFinished: false) + } + private func processSign(request: Request, session: Session?, dataSource: DAppStateDataSource) { guard let stateMachine = stateMachine else { return @@ -167,7 +207,19 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { case let .proposal(proposal): process(proposal: proposal, dataSource: dataSource) case let .request(request, session): - processSign(request: request, session: session, dataSource: dataSource) + guard let session = session else { + // TODO: No session found error + return + } + + fetchWallet(for: session, dataSource: dataSource) { [weak self] optWallet in + if let wallet = optWallet { + self?.processSign(request: request, session: session, dataSource: dataSource) + } else { + // TODO: Handle not authorized request + self?.rejectRequest(request: request) + } + } } } } diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index bc53a19c69..eb2808bc19 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1195,9 +1195,6 @@ "evm.contract.call" = "Contract call"; "evm.contract" = "Contract"; "evm.contract.function" = "Function"; -"common.wallet.connect" = "Wallet Connect"; -"wallet.connect.scan.message" = "Scan QR from DApp"; -"wallet.connect.scan.error" = "Invalid URI format"; "transfer.setup.kilt.addresses.title" = "%@ addresses for %@"; "transfer.setup.error.w3n.serviceNotFound.subtitle" = "No valid address was found for %@ on the %@ network"; "transfer.setup.error.w3n.accountNotFound.subtitle" = "%@ not found"; @@ -1211,3 +1208,7 @@ "transfer.setup.recipient.input.placeholder" = "Address or w3n"; "transfer.setup.error.w3n.search.in.progress.title" = "Searching name is in progress"; "transfer.setup.error.w3n.search.in.progress.subtitle" = "Please, wait until searching %@ is finished"; +"common.wallet.connect" = "WalletConnect"; +"wallet.connect.scan.message" = "Scan QR from DApp"; +"wallet.connect.scan.error" = "Invalid URI format"; +"wallet.connect.scan.button" = "New connection"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 1245ff30dd..3f7ade954e 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1195,9 +1195,6 @@ "evm.contract.call" = "Вызов контракта"; "evm.contract" = "Контракт"; "evm.contract.function" = "Функция"; -"common.wallet.connect" = "Wallet Connect"; -"wallet.connect.scan.message" = "Отсканируйте QR код"; -"wallet.connect.scan.error" = "Неверный формат URI"; "transfer.setup.kilt.addresses.title" = "%@ адреса для %@"; "transfer.setup.error.w3n.serviceNotFound.subtitle" = "Не найден действительный адрес для %@ в сети %@"; "transfer.setup.error.w3n.accountNotFound.subtitle" = "%@ не найден"; @@ -1211,3 +1208,6 @@ "transfer.setup.recipient.input.placeholder" = "Адрес или w3n"; "transfer.setup.error.w3n.search.in.progress.title" = "Идет поиск адреса получателя"; "transfer.setup.error.w3n.search.in.progress.subtitle" = "Пожалуйста, подождите пока завершится поиск имени %@"; +"wallet.connect.scan.message" = "Отсканируйте QR код"; +"wallet.connect.scan.error" = "Неверный формат URI"; +"wallet.connect.scan.button" = "Новое соединение"; diff --git a/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift b/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift deleted file mode 100644 index 2b34ff2ce3..0000000000 --- a/novawalletTests/Modules/WalletConnectSessions/WalletConnectSessionsTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -import XCTest - -class WalletConnectSessionsTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - XCTFail("Did you forget to add tests?") - } -} From 4132e2b54474bca56a369758a632849e9fd37321 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 3 May 2023 19:10:50 +0500 Subject: [PATCH 23/54] fetch sessions --- .../Model/WalletConnectSession.swift | 2 +- .../Service/WalletConnectInteractor.swift | 4 + .../Service/WalletConnectProtocols.swift | 2 + .../WalletConnectSessionsInteractor.swift | 9 ++- .../States/WalletConnectStateNewMessage.swift | 26 +++--- .../Transport/WalletConnectTransport.swift | 79 +++++++++++++++++++ 6 files changed, 111 insertions(+), 11 deletions(-) diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift index 1d3de36efe..cbe6835ffd 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift @@ -6,5 +6,5 @@ struct WalletConnectSession { let dAppName: String? let dAppHost: String? let dAppIcon: URL? - let isDAppActive: Bool + let active: Bool } diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift index 41923ac80b..bf9bb69f4b 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -40,6 +40,10 @@ extension WalletConnectInteractor: WalletConnectDelegateInputProtocol { func getSessionsCount() -> Int { transport.getSessionsCount() } + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) { + transport.fetchSessions(completion) + } } extension WalletConnectInteractor: WalletConnectTransportDelegate { diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift index 6c688f29cb..6ac02515e9 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift @@ -9,6 +9,8 @@ protocol WalletConnectDelegateInputProtocol: AnyObject { func remove(delegate: WalletConnectDelegateOutputProtocol) func getSessionsCount() -> Int + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) } protocol WalletConnectDelegateOutputProtocol: AnyObject { diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift index fc401f0387..b0c6a45130 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift @@ -8,6 +8,11 @@ final class WalletConnectSessionsInteractor { init(walletConnect: WalletConnectDelegateInputProtocol) { self.walletConnect = walletConnect } + + private func provideSessions() { + walletConnect.fetchSessions { _ in + } + } } extension WalletConnectSessionsInteractor: WalletConnectSessionsInteractorInputProtocol { @@ -21,5 +26,7 @@ extension WalletConnectSessionsInteractor: WalletConnectSessionsInteractorInputP } extension WalletConnectSessionsInteractor: WalletConnectDelegateOutputProtocol { - func walletConnectDidChangeSessions() {} + func walletConnectDidChangeSessions() { + provideSessions() + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index 7df042533c..055d693ded 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -121,7 +121,12 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { dataSource.operationQueue.addOperations(operations, waitUntilFinished: false) } - private func processSign(request: Request, session: Session?, dataSource: DAppStateDataSource) { + private func processSign( + request: Request, + session: Session?, + wallet: MetaAccountModel, + chainsStore: ChainsStoreProtocol + ) { guard let stateMachine = stateMachine else { return } @@ -134,30 +139,28 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { guard let chain = WalletConnectModelFactory.resolveChain( for: request.chainId, - chainsStore: dataSource.chainsStore + chainsStore: chainsStore ) else { rejectRequest(request: request) return } guard - let accountId = dataSource.walletSettings.value.fetch( - for: chain.accountRequest() - )?.accountId else { + let accountId = wallet.fetch(for: chain.accountRequest())?.accountId else { rejectRequest(request: request) return } do { let operationData = try WalletConnectSignModelFactory.createOperationData( - for: dataSource.walletSettings.value, + for: wallet, chain: chain, params: request.params, method: method ) let signingType = try WalletConnectSignModelFactory.createSigningType( - for: dataSource.walletSettings.value, + for: wallet, chain: chain, method: method ) @@ -165,7 +168,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { let signingRequest = DAppOperationRequest( transportName: DAppTransports.walletConnect, identifier: request.id.string, - wallet: dataSource.walletSettings.value, + wallet: wallet, accountId: accountId, dApp: session?.peer.name ?? "", dAppIcon: session?.peer.icons.first.flatMap { URL(string: $0) }, @@ -214,7 +217,12 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { fetchWallet(for: session, dataSource: dataSource) { [weak self] optWallet in if let wallet = optWallet { - self?.processSign(request: request, session: session, dataSource: dataSource) + self?.processSign( + request: request, + session: session, + wallet: wallet, + chainsStore: dataSource.chainsStore + ) } else { // TODO: Handle not authorized request self?.rejectRequest(request: request) diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 3999561dd2..af402f4104 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -1,5 +1,6 @@ import Foundation import WalletConnectSwiftV2 +import RobinHood protocol WalletConnectTransportProtocol: DAppTransportProtocol { var delegate: WalletConnectTransportDelegate? { get set } @@ -7,6 +8,8 @@ protocol WalletConnectTransportProtocol: DAppTransportProtocol { func connect(uri: String) func getSessionsCount() -> Int + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) } protocol WalletConnectTransportDelegate: AnyObject { @@ -48,6 +51,41 @@ final class WalletConnectTransport { self.dataSource = dataSource self.logger = logger } + + private func createSessionsMappingOperation( + dependingOn allSettingsOperation: BaseOperation<[DAppSettings]>, + allWalletsOperation: BaseOperation<[MetaAccountModel]>, + wcSessions: [Session] + ) -> BaseOperation<[WalletConnectSession]> { + ClosureOperation<[WalletConnectSession]> { + let allSettings = try allSettingsOperation.extractNoCancellableResultData().reduceToDict() + let allWallets = try allWalletsOperation.extractNoCancellableResultData().reduceToDict() + + return wcSessions.map { wcSession in + let dAppIcon = wcSession.peer.icons.first.flatMap { URL(string: $0) } + let active = wcSession.expiryDate.compare(Date()) != .orderedDescending + + let wallet: MetaAccountModel? + + if + let settings = allSettings[wcSession.pairingTopic], + let metaId = settings.metaId { + wallet = allWallets[metaId] + } else { + wallet = nil + } + + return WalletConnectSession( + sessionId: wcSession.pairingTopic, + wallet: wallet, + dAppName: wcSession.peer.name, + dAppHost: wcSession.peer.url, + dAppIcon: dAppIcon, + active: active + ) + } + } + } } extension WalletConnectTransport: WalletConnectTransportProtocol { @@ -58,6 +96,47 @@ extension WalletConnectTransport: WalletConnectTransportProtocol { func getSessionsCount() -> Int { service.getSessions().count } + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) { + let wcSessions = service.getSessions() + + guard !wcSessions.isEmpty else { + completion(.success([])) + return + } + + let allSettingsOperation = dataSource.dAppSettingsRepository.fetchAllOperation(with: .init()) + let allWalletsOperation = dataSource.walletsRepository.fetchAllOperation(with: .init()) + + let mapOperation = createSessionsMappingOperation( + dependingOn: allSettingsOperation, + allWalletsOperation: allWalletsOperation, + wcSessions: wcSessions + ) + + mapOperation.addDependency(allSettingsOperation) + mapOperation.addDependency(allWalletsOperation) + + let operations = [allSettingsOperation, allWalletsOperation, mapOperation] + + mapOperation.completionBlock = { [weak self] in + DispatchQueue.main.async { + guard let self = self else { + return + } + + do { + let sessions = try mapOperation.extractNoCancellableResultData() + + completion(.success(sessions)) + } catch { + completion(.failure(error)) + } + } + } + + dataSource.operationQueue.addOperations(operations, waitUntilFinished: false) + } } extension WalletConnectTransport { From c9e1aae47db1dfb1c6b4969fc5e29769556780b8 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 4 May 2023 11:09:31 +0500 Subject: [PATCH 24/54] add sessions list ui --- novawallet.xcodeproj/project.pbxproj | 28 +++++++++ novawallet/Common/View/IconDetailsView.swift | 19 ++++++ .../PlainBaseTableViewCell.swift | 39 ++++++++++++ .../WalletAccountViewModelFactory.swift | 7 +++ .../View/DAppAuthSettingsTableCell.swift | 26 ++++---- .../Model/WalletConnectSession.swift | 1 + .../View/WalletConnectSessionCell.swift | 60 +++++++++++++++++++ .../WalletConnectSessionListViewModel.swift | 16 +++++ ...alletConnectSessionsViewModelFactory.swift | 31 ++++++++++ .../WalletConnectSessionsInteractor.swift | 14 ++++- ...WalletConnectSessionsInteractorError.swift | 5 ++ .../WalletConnectSessionsPresenter.swift | 53 +++++++++++++++- .../WalletConnectSessionsProtocols.swift | 17 +++++- .../WalletConnectSessionsViewController.swift | 47 ++++++++++++++- .../WalletConnectSessionsViewFactory.swift | 10 +++- .../WalletConnectSessionsViewLayout.swift | 1 + .../WalletConnectSessionsWireframe.swift | 10 +++- .../Transport/WalletConnectTransport.swift | 3 +- 18 files changed, 364 insertions(+), 23 deletions(-) create mode 100644 novawallet/Common/View/TableViewCell/PlainBaseTableViewCell.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionListViewModel.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionsViewModelFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 91f0e77e98..8194cd5637 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -2135,6 +2135,11 @@ 84C9CF3B291AE328002BF328 /* GovernanceV1PolkassemblyOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9CF3A291AE328002BF328 /* GovernanceV1PolkassemblyOperationFactory.swift */; }; 84C9CF3D291AF1B1002BF328 /* GovernanceOffchainApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9CF3C291AF1B1002BF328 /* GovernanceOffchainApi.swift */; }; 84C9CF3F291AF4B2002BF328 /* ReferendumMetadataDetailsProviderSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9CF3E291AF4B2002BF328 /* ReferendumMetadataDetailsProviderSource.swift */; }; + 84CA40752A035DA1004BB71E /* WalletConnectSessionsInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA40742A035DA1004BB71E /* WalletConnectSessionsInteractorError.swift */; }; + 84CA40782A0366E1004BB71E /* WalletConnectSessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA40772A0366E1004BB71E /* WalletConnectSessionCell.swift */; }; + 84CA407A2A036886004BB71E /* PlainBaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA40792A036886004BB71E /* PlainBaseTableViewCell.swift */; }; + 84CA407C2A037175004BB71E /* WalletConnectSessionListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA407B2A037175004BB71E /* WalletConnectSessionListViewModel.swift */; }; + 84CA407E2A037658004BB71E /* WalletConnectSessionsViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA407D2A037658004BB71E /* WalletConnectSessionsViewModelFactory.swift */; }; 84CA68CF26BD6872003B9453 /* RuntimeSyncService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA68CE26BD6872003B9453 /* RuntimeSyncService.swift */; }; 84CA68D126BE99ED003B9453 /* RuntimeProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA68D026BE99ED003B9453 /* RuntimeProviderFactory.swift */; }; 84CA68D326BE9A35003B9453 /* RuntimeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA68D226BE9A35003B9453 /* RuntimeProvider.swift */; }; @@ -5592,6 +5597,11 @@ 84C9CF3C291AF1B1002BF328 /* GovernanceOffchainApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainApi.swift; sourceTree = ""; }; 84C9CF3E291AF4B2002BF328 /* ReferendumMetadataDetailsProviderSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumMetadataDetailsProviderSource.swift; sourceTree = ""; }; 84CA40732A0274C2004BB71E /* MultiassetUserDataModel8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MultiassetUserDataModel8.xcdatamodel; sourceTree = ""; }; + 84CA40742A035DA1004BB71E /* WalletConnectSessionsInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsInteractorError.swift; sourceTree = ""; }; + 84CA40772A0366E1004BB71E /* WalletConnectSessionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionCell.swift; sourceTree = ""; }; + 84CA40792A036886004BB71E /* PlainBaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlainBaseTableViewCell.swift; sourceTree = ""; }; + 84CA407B2A037175004BB71E /* WalletConnectSessionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionListViewModel.swift; sourceTree = ""; }; + 84CA407D2A037658004BB71E /* WalletConnectSessionsViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionsViewModelFactory.swift; sourceTree = ""; }; 84CA68CE26BD6872003B9453 /* RuntimeSyncService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeSyncService.swift; sourceTree = ""; }; 84CA68D026BE99ED003B9453 /* RuntimeProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeProviderFactory.swift; sourceTree = ""; }; 84CA68D226BE9A35003B9453 /* RuntimeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeProvider.swift; sourceTree = ""; }; @@ -8029,6 +8039,7 @@ 849842E526587573006BBB9F /* MultilineTableViewCell.swift */, 84C91FA9261E724F002796B9 /* SwitchTableViewCell.swift */, 84276686297AB22B0063E08E /* TableViewCellPosition.swift */, + 84CA40792A036886004BB71E /* PlainBaseTableViewCell.swift */, ); path = TableViewCell; sourceTree = ""; @@ -13231,6 +13242,16 @@ path = GovMetadataOperationFactory; sourceTree = ""; }; + 84CA40762A0366C3004BB71E /* View */ = { + isa = PBXGroup; + children = ( + 84CA40772A0366E1004BB71E /* WalletConnectSessionCell.swift */, + 84CA407B2A037175004BB71E /* WalletConnectSessionListViewModel.swift */, + 84CA407D2A037658004BB71E /* WalletConnectSessionsViewModelFactory.swift */, + ); + path = View; + sourceTree = ""; + }; 84CAF52524BEEF2F00F4C295 /* JSONRPC */ = { isa = PBXGroup; children = ( @@ -15942,6 +15963,7 @@ EBA3D6C1965795AF75E2A18B /* Sessions */ = { isa = PBXGroup; children = ( + 84CA40762A0366C3004BB71E /* View */, 1AE2D71C8E0A7FD346D2654C /* WalletConnectSessionsProtocols.swift */, 52CC2C3981590DCEDE3ABBEC /* WalletConnectSessionsWireframe.swift */, 82445792D5EF0120B5233767 /* WalletConnectSessionsPresenter.swift */, @@ -15949,6 +15971,7 @@ 9ADD21F058AE84F533353158 /* WalletConnectSessionsViewController.swift */, E697F5EF3AD8EE7281FBF6CB /* WalletConnectSessionsViewLayout.swift */, 0AC34A44F63EFAE6E804BDE9 /* WalletConnectSessionsViewFactory.swift */, + 84CA40742A035DA1004BB71E /* WalletConnectSessionsInteractorError.swift */, ); path = Sessions; sourceTree = ""; @@ -17014,6 +17037,7 @@ 84953F6C2935E1A80033F47D /* UserAgent.swift in Sources */, 84906FEC28AFD23B0049B57D /* LoadableStackActionCell.swift in Sources */, 8487010A2907055900F2C0C3 /* ReferendumTimelineViewModelFactory.swift in Sources */, + 84CA407C2A037175004BB71E /* WalletConnectSessionListViewModel.swift in Sources */, AEF73FB525DBA24300407D41 /* PhishingCheckExecutor.swift in Sources */, AEE5FB1826457AC1002B8FDC /* StakingRewardDestSetupViewController.swift in Sources */, 846AF8462525C93A00868F37 /* BalanceContext.swift in Sources */, @@ -17401,6 +17425,7 @@ 849ABE49262763BB00011A2A /* Longrun.swift in Sources */, 8490111129E5A509005D688B /* URIScanPresenter.swift in Sources */, 84644A30256722D2004EAA4B /* TriangularedBlurButton+Inspectable.swift in Sources */, + 84CA407E2A037658004BB71E /* WalletConnectSessionsViewModelFactory.swift in Sources */, 844DBC60274D1B3E009F8351 /* IconWithTitleSubtitleViewModel.swift in Sources */, 849014B924AA87E3008F705E /* PinSetupViewController.swift in Sources */, 84F98D8A25E3DD3F0040418E /* StorageCodingPath.swift in Sources */, @@ -17535,6 +17560,7 @@ 84B6349F28F5575900503306 /* PreimageRequestStatus.swift in Sources */, F4F69E282731B0B200214542 /* VoteTableHeaderView.swift in Sources */, 8428768624AE046300D91AD8 /* LanguageSelectionPresenter.swift in Sources */, + 84CA407A2A036886004BB71E /* PlainBaseTableViewCell.swift in Sources */, 8490152724ABCC40008F705E /* NumberFormatter.swift in Sources */, 846A2C4325271CDE00731018 /* TransactionType.swift in Sources */, 84893C0124DA861D008F6A3F /* AccountCreationMetadata.swift in Sources */, @@ -19498,6 +19524,7 @@ 84350AD62845836C0031EF24 /* IdentityPresentable.swift in Sources */, 8A19EC93E6A6972327116D80 /* ParaStkStakeConfirmProtocols.swift in Sources */, C18124044388698CB0BE8947 /* ParaStkStakeConfirmWireframe.swift in Sources */, + 84CA40782A0366E1004BB71E /* WalletConnectSessionCell.swift in Sources */, 8EECC23DA32547DAAFC260BE /* ParaStkStakeConfirmPresenter.swift in Sources */, EFEB65B229DB34B4B526003B /* ParaStkStakeConfirmInteractor.swift in Sources */, EE0DFA5851AEF99D3C2DBDDD /* ParaStkStakeConfirmViewController.swift in Sources */, @@ -19770,6 +19797,7 @@ 2BBD2FAA29C71DC6E0C8A845 /* ParaStkYieldBoostStopPresenter.swift in Sources */, 68BE7C4037E8A5C245CDFF0D /* ParaStkYieldBoostStopInteractor.swift in Sources */, A2BE8967FC1609D61E4131BE /* ParaStkYieldBoostStopViewController.swift in Sources */, + 84CA40752A035DA1004BB71E /* WalletConnectSessionsInteractorError.swift in Sources */, 7584B6DC2C7F8B2B6671908F /* ParaStkYieldBoostStopViewLayout.swift in Sources */, 3E0DDDE01391F9041C8D7382 /* ParaStkYieldBoostStopViewFactory.swift in Sources */, 0B48B02E973CB304B765BBC9 /* ReferendumDetailsProtocols.swift in Sources */, diff --git a/novawallet/Common/View/IconDetailsView.swift b/novawallet/Common/View/IconDetailsView.swift index 33bb9b6547..60651947b1 100644 --- a/novawallet/Common/View/IconDetailsView.swift +++ b/novawallet/Common/View/IconDetailsView.swift @@ -127,3 +127,22 @@ extension IconDetailsView { detailsLabel.text = viewModel?.title } } + +class LoadableIconDetailsView: IconDetailsView { + private var imageViewModel: ImageViewModelProtocol? + + func bind(viewModel: StackCellViewModel?) { + imageViewModel?.cancel(on: imageView) + imageView.image = nil + + detailsLabel.text = viewModel?.details + + imageViewModel = viewModel?.imageViewModel + + viewModel?.imageViewModel?.loadImage( + on: imageView, + targetSize: CGSize(width: iconWidth, height: iconWidth), + animated: true + ) + } +} diff --git a/novawallet/Common/View/TableViewCell/PlainBaseTableViewCell.swift b/novawallet/Common/View/TableViewCell/PlainBaseTableViewCell.swift new file mode 100644 index 0000000000..b28e1dad78 --- /dev/null +++ b/novawallet/Common/View/TableViewCell/PlainBaseTableViewCell.swift @@ -0,0 +1,39 @@ +import UIKit + +class PlainBaseTableViewCell: UITableViewCell { + let contentDisplayView = C() + + private(set) var contentInsets = UIEdgeInsets( + top: 0, + left: UIConstants.horizontalInset, + bottom: 0, + right: UIConstants.horizontalInset + ) + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + setupStyle() + setupLayout() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupStyle() { + let selectionView = UIView() + selectionView.backgroundColor = R.color.colorCellBackgroundPressed() + + selectedBackgroundView = selectionView + } + + func setupLayout() { + contentView.addSubview(contentDisplayView) + + contentDisplayView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(contentInsets) + } + } +} diff --git a/novawallet/Common/View/WalletAccount/WalletAccountViewModelFactory.swift b/novawallet/Common/View/WalletAccount/WalletAccountViewModelFactory.swift index 2b7129d18c..cb6aa4078d 100644 --- a/novawallet/Common/View/WalletAccount/WalletAccountViewModelFactory.swift +++ b/novawallet/Common/View/WalletAccount/WalletAccountViewModelFactory.swift @@ -57,6 +57,13 @@ final class WalletAccountViewModelFactory { return DisplayWalletViewModel(name: response.chainAccount.name, imageViewModel: iconViewModel) } + func createDisplayViewModel(from wallet: MetaAccountModel) throws -> DisplayWalletViewModel { + let walletIcon = wallet.walletIdenticonData().flatMap { try? walletIconGenerator.generateFromAccountId($0) } + let iconViewModel = walletIcon.map { DrawableIconViewModel(icon: $0) } + + return DisplayWalletViewModel(name: wallet.name, imageViewModel: iconViewModel) + } + func createViewModel(from validatorInfo: ValidatorInfoProtocol) -> WalletAccountViewModel { do { let walletIconViewModel: ImageViewModelProtocol? diff --git a/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift b/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift index ea57574277..042070156f 100644 --- a/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift +++ b/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift @@ -5,21 +5,21 @@ protocol DAppAuthSettingsTableCellDelegate: AnyObject { func authSettingsDidSelectCell(_ cell: DAppAuthSettingsTableCell) } -final class DAppAuthSettingsTableCell: UITableViewCell { - private enum Constants { - static let imageSize = CGSize(width: 48.0, height: 48.0) - static let imageInsets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 6.0) - static var imageDisplaySize: CGSize { - CGSize( - width: imageSize.width - imageInsets.left - imageInsets.right, - height: imageSize.height - imageInsets.top - imageInsets.bottom - ) - } +enum DAppIconCellConstants { + static let size = CGSize(width: 48.0, height: 48.0) + static let insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 6.0) + static var displaySize: CGSize { + CGSize( + width: size.width - insets.left - insets.right, + height: size.height - insets.top - insets.bottom + ) } +} +final class DAppAuthSettingsTableCell: UITableViewCell { let iconView: DAppIconView = { let view = DAppIconView() - view.contentInsets = Constants.imageInsets + view.contentInsets = DAppIconCellConstants.insets return view }() @@ -59,7 +59,7 @@ final class DAppAuthSettingsTableCell: UITableViewCell { } func bind(viewModel: DAppAuthSettingsViewModel) { - iconView.bind(viewModel: viewModel.iconViewModel, size: Constants.imageDisplaySize) + iconView.bind(viewModel: viewModel.iconViewModel, size: DAppIconCellConstants.displaySize) multiValueView.bind(topValue: viewModel.title, bottomValue: viewModel.subtitle) } @@ -78,7 +78,7 @@ final class DAppAuthSettingsTableCell: UITableViewCell { make.top.equalToSuperview().inset(8.0) make.leading.equalToSuperview().inset(16.0) make.bottom.equalToSuperview().inset(8.0) - make.size.equalTo(48.0) + make.size.equalTo(DAppIconCellConstants.size) } contentView.addSubview(removeButton) diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift index cbe6835ffd..72c01a6c75 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift @@ -2,6 +2,7 @@ import Foundation struct WalletConnectSession { let sessionId: String + let pairingId: String let wallet: MetaAccountModel? let dAppName: String? let dAppHost: String? diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift new file mode 100644 index 0000000000..8480def260 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift @@ -0,0 +1,60 @@ +import UIKit + +typealias WalletConnectSessionCellContent = GenericTitleValueView< + GenericPairValueView>, + UIImageView +> + +final class WalletConnectSessionCell: PlainBaseTableViewCell { + var iconView: DAppIconView { + contentDisplayView.titleView.fView + } + + var titleLabel: UILabel { + contentDisplayView.titleView.sView.valueTop + } + + var walletView: LoadableIconDetailsView { + contentDisplayView.titleView.sView.valueBottom + } + + var accessoryImageView: UIImageView { + contentDisplayView.valueView + } + + override func setupStyle() { + super.setupStyle() + + iconView.contentInsets = DAppIconCellConstants.insets + + contentDisplayView.titleView.setHorizontalAndSpacing(12) + contentDisplayView.titleView.stackView.alignment = .center + + titleLabel.apply(style: .regularSubhedlinePrimary) + + contentDisplayView.titleView.sView.spacing = 2.0 + + walletView.iconWidth = 16 + walletView.spacing = 8 + walletView.detailsLabel.numberOfLines = 1 + walletView.detailsLabel.apply(style: .caption1Secondary) + + accessoryImageView.image = R.image.iconChevronRight()?.tinted(with: R.color.colorIconSecondary()!) + } + + override func setupLayout() { + super.setupLayout() + + iconView.snp.makeConstraints { make in + make.size.equalTo(DAppIconCellConstants.size) + } + } + + func bind(viewModel: WalletConnectSessionListViewModel) { + iconView.bind(viewModel: viewModel.iconViewModel, size: DAppIconCellConstants.displaySize) + + titleLabel.text = viewModel.title + + walletView.bind(viewModel: viewModel.wallet?.cellViewModel) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionListViewModel.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionListViewModel.swift new file mode 100644 index 0000000000..079c42a3b2 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionListViewModel.swift @@ -0,0 +1,16 @@ +import Foundation + +struct WalletConnectSessionListViewModel: Hashable { + let identifier: String + let iconViewModel: ImageViewModelProtocol + let title: String + let wallet: DisplayWalletViewModel? + + static func == (lhs: WalletConnectSessionListViewModel, rhs: WalletConnectSessionListViewModel) -> Bool { + lhs.identifier == rhs.identifier + } + + func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionsViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionsViewModelFactory.swift new file mode 100644 index 0000000000..13ee43921b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionsViewModelFactory.swift @@ -0,0 +1,31 @@ +import Foundation + +protocol WalletConnectSessionsViewModelFactoryProtocol { + func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionListViewModel +} + +final class WalletConnectSessionsViewModelFactory: WalletConnectSessionsViewModelFactoryProtocol { + let walletViewModelFactory = WalletAccountViewModelFactory() + + func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionListViewModel { + let walletViewModel: DisplayWalletViewModel? = model.wallet.flatMap { wallet in + try? walletViewModelFactory.createDisplayViewModel(from: wallet) + } + + let iconViewModel: ImageViewModelProtocol + + if let icon = model.dAppIcon { + iconViewModel = RemoteImageViewModel(url: icon) + } else { + let icon = R.image.iconDefaultDapp()! + iconViewModel = StaticImageViewModel(image: icon) + } + + return .init( + identifier: model.sessionId, + iconViewModel: iconViewModel, + title: model.dAppName ?? model.dAppHost ?? "", + wallet: walletViewModel + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift index b0c6a45130..3e61bc0b1e 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift @@ -10,7 +10,13 @@ final class WalletConnectSessionsInteractor { } private func provideSessions() { - walletConnect.fetchSessions { _ in + walletConnect.fetchSessions { [weak self] result in + switch result { + case let .success(sessions): + self?.presenter?.didReceive(sessions: sessions) + case let .failure(error): + self?.presenter?.didReceive(error: .sessionsFetchFailed(error)) + } } } } @@ -18,11 +24,17 @@ final class WalletConnectSessionsInteractor { extension WalletConnectSessionsInteractor: WalletConnectSessionsInteractorInputProtocol { func setup() { walletConnect.add(delegate: self) + + provideSessions() } func connect(uri: String) { walletConnect.connect(uri: uri) } + + func retrySessionsFetch() { + provideSessions() + } } extension WalletConnectSessionsInteractor: WalletConnectDelegateOutputProtocol { diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift new file mode 100644 index 0000000000..dfc28e73ff --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift @@ -0,0 +1,5 @@ +import Foundation + +enum WalletConnectSessionsInteractorError: Error { + case sessionsFetchFailed(Error) +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift index bcbc878322..54d61e9cab 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift @@ -1,20 +1,36 @@ import Foundation +import SoraFoundation final class WalletConnectSessionsPresenter { weak var view: WalletConnectSessionsViewProtocol? let wireframe: WalletConnectSessionsWireframeProtocol let interactor: WalletConnectSessionsInteractorInputProtocol + let viewModelFactory: WalletConnectSessionsViewModelFactoryProtocol let logger: LoggerProtocol + private var sessions: [WalletConnectSession]? + init( interactor: WalletConnectSessionsInteractorInputProtocol, wireframe: WalletConnectSessionsWireframeProtocol, + viewModelFactory: WalletConnectSessionsViewModelFactoryProtocol, + localizationManager: LocalizationManagerProtocol, logger: LoggerProtocol ) { self.interactor = interactor self.wireframe = wireframe + self.viewModelFactory = viewModelFactory self.logger = logger + self.localizationManager = localizationManager + } + + private func updateView() { + let viewModels = (sessions ?? []).map { session in + viewModelFactory.createViewModel(from: session) + } + + view?.didReceive(viewModels: viewModels) } } @@ -26,9 +42,36 @@ extension WalletConnectSessionsPresenter: WalletConnectSessionsPresenterProtocol func showScan() { wireframe.showScan(from: view, delegate: self) } + + func showSession(at index: Int) { + if let session = sessions?[index] { + wireframe.showSession(from: view, details: session) + } + } } -extension WalletConnectSessionsPresenter: WalletConnectSessionsInteractorOutputProtocol {} +extension WalletConnectSessionsPresenter: WalletConnectSessionsInteractorOutputProtocol { + func didReceive(sessions: [WalletConnectSession]) { + self.sessions = sessions + + updateView() + + if sessions.isEmpty { + wireframe.close(view: view) + } + } + + func didReceive(error: WalletConnectSessionsInteractorError) { + logger.error("Did receive error: \(error)") + + switch error { + case .sessionsFetchFailed: + wireframe.presentRequestStatus(on: view, locale: selectedLocale) { [weak self] in + self?.interactor.retrySessionsFetch() + } + } + } +} extension WalletConnectSessionsPresenter: URIScanDelegate { func uriScanDidReceive(uri: String, context _: AnyObject?) { @@ -39,3 +82,11 @@ extension WalletConnectSessionsPresenter: URIScanDelegate { } } } + +extension WalletConnectSessionsPresenter: Localizable { + func applyLocalization() { + if let view = view, view.isSetup { + updateView() + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift index d12cf3f359..c5657536ae 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift @@ -1,15 +1,26 @@ -protocol WalletConnectSessionsViewProtocol: ControllerBackedProtocol {} +protocol WalletConnectSessionsViewProtocol: ControllerBackedProtocol { + func didReceive(viewModels: [WalletConnectSessionListViewModel]) +} protocol WalletConnectSessionsPresenterProtocol: AnyObject { func setup() func showScan() + func showSession(at index: Int) } protocol WalletConnectSessionsInteractorInputProtocol: AnyObject { func setup() func connect(uri: String) + func retrySessionsFetch() } -protocol WalletConnectSessionsInteractorOutputProtocol: AnyObject {} +protocol WalletConnectSessionsInteractorOutputProtocol: AnyObject { + func didReceive(sessions: [WalletConnectSession]) + func didReceive(error: WalletConnectSessionsInteractorError) +} -protocol WalletConnectSessionsWireframeProtocol: WalletConnectScanPresentable {} +protocol WalletConnectSessionsWireframeProtocol: WalletConnectScanPresentable, AlertPresentable, + ErrorPresentable, CommonRetryable { + func showSession(from view: WalletConnectSessionsViewProtocol?, details: WalletConnectSession) + func close(view: WalletConnectSessionsViewProtocol?) +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift index b39a9d4165..48f0e8ebf0 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewController.swift @@ -6,9 +6,20 @@ final class WalletConnectSessionsViewController: UIViewController, ViewHolder { let presenter: WalletConnectSessionsPresenterProtocol - init(presenter: WalletConnectSessionsPresenterProtocol) { + typealias DataSource = UITableViewDiffableDataSource + + typealias Snapshot = NSDiffableDataSourceSnapshot + + private lazy var dataSource = createDataSource() + + init( + presenter: WalletConnectSessionsPresenterProtocol, + localizationManager: LocalizationManagerProtocol + ) { self.presenter = presenter super.init(nibName: nil, bundle: nil) + + self.localizationManager = localizationManager } @available(*, unavailable) @@ -24,11 +35,27 @@ final class WalletConnectSessionsViewController: UIViewController, ViewHolder { super.viewDidLoad() setupLocalization() + setupTableView() setupHandlers() presenter.setup() } + private func createDataSource() -> DataSource { + .init(tableView: rootView.tableView) { tableView, indexPath, viewModel -> UITableViewCell? in + let cell: WalletConnectSessionCell = tableView.dequeueReusableCell(for: indexPath) + cell.bind(viewModel: viewModel) + return cell + } + } + + private func setupTableView() { + rootView.tableView.dataSource = dataSource + rootView.tableView.delegate = self + + rootView.tableView.registerClassForCell(WalletConnectSessionCell.self) + } + private func setupHandlers() { rootView.scanButton.addTarget( self, @@ -54,7 +81,23 @@ final class WalletConnectSessionsViewController: UIViewController, ViewHolder { } } -extension WalletConnectSessionsViewController: WalletConnectSessionsViewProtocol {} +extension WalletConnectSessionsViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + presenter.showSession(at: indexPath.row) + } +} + +extension WalletConnectSessionsViewController: WalletConnectSessionsViewProtocol { + func didReceive(viewModels: [WalletConnectSessionListViewModel]) { + var snapshot = Snapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(viewModels) + + dataSource.apply(snapshot, animatingDifferences: false) + } +} extension WalletConnectSessionsViewController: Localizable { func applyLocalization() { diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift index dfe72b113b..40899b43f1 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift @@ -1,4 +1,5 @@ import Foundation +import SoraFoundation struct WalletConnectSessionsViewFactory { static func createView( @@ -10,13 +11,20 @@ struct WalletConnectSessionsViewFactory { let wireframe = WalletConnectSessionsWireframe() + let localizationManager = LocalizationManager.shared + let presenter = WalletConnectSessionsPresenter( interactor: interactor, wireframe: wireframe, + viewModelFactory: WalletConnectSessionsViewModelFactory(), + localizationManager: localizationManager, logger: Logger.shared ) - let view = WalletConnectSessionsViewController(presenter: presenter) + let view = WalletConnectSessionsViewController( + presenter: presenter, + localizationManager: localizationManager + ) presenter.view = view interactor.presenter = presenter diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift index 037bbfb1ca..a0a22258ec 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewLayout.swift @@ -15,6 +15,7 @@ final class WalletConnectSessionsViewLayout: UIView { let view = UITableView() view.separatorStyle = .none view.backgroundColor = .clear + view.rowHeight = 64 return view }() diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift index e4d48ff7aa..958fb58bf1 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift @@ -1,3 +1,11 @@ import Foundation -final class WalletConnectSessionsWireframe: WalletConnectSessionsWireframeProtocol {} +final class WalletConnectSessionsWireframe: WalletConnectSessionsWireframeProtocol { + func showSession(from _: WalletConnectSessionsViewProtocol?, details _: WalletConnectSession) { + // TODO: Present session details + } + + func close(view: WalletConnectSessionsViewProtocol?) { + view?.controller.navigationController?.popViewController(animated: true) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index af402f4104..37887f7bc4 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -76,7 +76,8 @@ final class WalletConnectTransport { } return WalletConnectSession( - sessionId: wcSession.pairingTopic, + sessionId: wcSession.topic, + pairingId: wcSession.pairingTopic, wallet: wallet, dAppName: wcSession.peer.name, dAppHost: wcSession.peer.url, From 5b5b31006512f855f275da0e49ad25c73488003e Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 5 May 2023 14:05:16 +0500 Subject: [PATCH 25/54] session details ui and logic --- novawallet.xcodeproj/project.pbxproj | 98 +++++- .../Extension/UIKit/Style/UILabel+Style.swift | 5 + .../UIKit/TriangularedButton+Style.swift | 17 + .../WalletConnect/WalletConnectService.swift | 32 +- .../Common/View/GlowingStatusView.swift | 47 +++ .../ScrollableContainerLayoutView.swift | 63 ++++ .../ScrollableContainerView.swift | 0 .../View/StackTable/StackStatusCell.swift | 34 ++ .../View/DAppAuthSettingsTableCell.swift | 11 - .../Modules/DApp/View/DAppIconView.swift | 22 ++ .../Service/WalletConnectInteractor.swift | 4 + .../Service/WalletConnectProtocols.swift | 2 + .../WalletConnectSessionViewModel.swift | 15 + ...WalletConnectSessionViewModelFactory.swift | 33 ++ ...alletConnectSessionDetailsInteractor.swift | 52 +++ ...ConnectSessionDetailsInteractorError.swift | 6 + ...WalletConnectSessionDetailsPresenter.swift | 98 ++++++ ...WalletConnectSessionDetailsProtocols.swift | 26 ++ ...tConnectSessionDetailsViewController.swift | 104 ++++++ ...lletConnectSessionDetailsViewFactory.swift | 50 +++ ...alletConnectSessionDetailsViewLayout.swift | 56 ++++ ...WalletConnectSessionDetailsWireframe.swift | 7 + .../WalletConnectSessionsViewFactory.swift | 2 +- .../WalletConnectSessionsWireframe.swift | 23 +- .../Transport/WalletConnectTransport.swift | 16 +- .../StackInfoTableCell+WallectConnect.swift | 10 + .../StackStatusCell+WalletConnect.swift | 18 ++ .../WalletConnectNetworksViewModel.swift | 11 + novawallet/en.lproj/Localizable.strings | 4 + novawallet/en.lproj/Localizable.stringsdict | 246 +++++++------- novawallet/ru.lproj/Localizable.strings | 4 + novawallet/ru.lproj/Localizable.stringsdict | 302 +++++++++--------- 32 files changed, 1133 insertions(+), 285 deletions(-) create mode 100644 novawallet/Common/View/GlowingStatusView.swift create mode 100644 novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift rename novawallet/Common/View/{ => ScrollableContainerView}/ScrollableContainerView.swift (100%) create mode 100644 novawallet/Common/View/StackTable/StackStatusCell.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewFactory.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/ViewModel/StackStatusCell+WalletConnect.swift create mode 100644 novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 8194cd5637..8b1d51bd41 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 0909E06D5D06569554F70DD8 /* AssetsSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44EAF50AAF6C23225E06C16C /* AssetsSearchInteractor.swift */; }; 09A6D92CE47636723DFC91F4 /* MessageSheetViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57535268534B154B42ED51CE /* MessageSheetViewFactory.swift */; }; 09AB6DE2D19F1FA36BF08288 /* StakingRebagConfirmViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D089568ABA686D509DF917 /* StakingRebagConfirmViewLayout.swift */; }; + 0A44D28DF4BCF56131752F35 /* WalletConnectSessionDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75ADB95DAB1F29E6A3FDD166 /* WalletConnectSessionDetailsPresenter.swift */; }; 0AAFEFA17F249F4BEF051F6B /* ControllerAccountConfirmationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FB887490A8B33890B4E0E4 /* ControllerAccountConfirmationPresenter.swift */; }; 0B2B9C6E2BA2E924D6A54F4B /* CrowdloanListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E78D69E8EBC3EB4D01F8EF /* CrowdloanListInteractor.swift */; }; 0B48B02E973CB304B765BBC9 /* ReferendumDetailsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ABAD23C0039AFA8351C650 /* ReferendumDetailsProtocols.swift */; }; @@ -270,6 +271,7 @@ 542588DA751A44C993BC1F27 /* ParaStkYourCollatorsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C13B77688FFF0FFBBB6612 /* ParaStkYourCollatorsWireframe.swift */; }; 5443122935BBFDD55AE9E6FD /* ParitySignerAddressesProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4F3C080F3D5C1E64475903 /* ParitySignerAddressesProtocols.swift */; }; 544C8EB3D71227FAF2FD4658 /* GovernanceRemoveVotesConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29FE1BF422468BECDCDEE63 /* GovernanceRemoveVotesConfirmPresenter.swift */; }; + 54983C354F7EDCD8014C8371 /* WalletConnectSessionDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE103341935B2A4B8C32B966 /* WalletConnectSessionDetailsViewFactory.swift */; }; 54D334605E9A7C71A4873CFC /* ParaStkRedeemWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578743E9101B334BFBE44CB6 /* ParaStkRedeemWireframe.swift */; }; 5510625BDA756B939ED7C586 /* AddDelegationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D44AF5C59681B54ECD7658 /* AddDelegationPresenter.swift */; }; 5619C10BFFAEAF89883227B4 /* WalletConnectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */; }; @@ -1090,6 +1092,7 @@ 84592F4C298716540001BB56 /* GovernanceOffchainVotingFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F4B298716540001BB56 /* GovernanceOffchainVotingFactory.swift */; }; 84592F4F298716E80001BB56 /* GovernanceOffchainVoting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F4E298716E80001BB56 /* GovernanceOffchainVoting.swift */; }; 84592F512987B2E80001BB56 /* SubqueryVotingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F502987B2E80001BB56 /* SubqueryVotingResponse.swift */; }; + 845938842A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */; }; 8459A9C827469E4B000D6278 /* AcalaContributionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */; }; 8459A9CA2746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9C92746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift */; }; 8459A9CC2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9CB2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift */; }; @@ -2207,6 +2210,14 @@ 84D17EDC28054C9C00F7BAFF /* DAppLocalSubscriptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D17EDB28054C9C00F7BAFF /* DAppLocalSubscriptionHandler.swift */; }; 84D17EDE28054CC400F7BAFF /* DAppLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D17EDD28054CC400F7BAFF /* DAppLocalSubscriptionFactory.swift */; }; 84D17EE12805A62600F7BAFF /* DAppAlertPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D17EE02805A62600F7BAFF /* DAppAlertPresentable.swift */; }; + 84D184E82A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184E72A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift */; }; + 84D184EA2A04D9980060C1BD /* StackStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184E92A04D9980060C1BD /* StackStatusCell.swift */; }; + 84D184EC2A04DA810060C1BD /* GlowingStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184EB2A04DA810060C1BD /* GlowingStatusView.swift */; }; + 84D184EF2A04E2760060C1BD /* WalletConnectSessionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184EE2A04E2760060C1BD /* WalletConnectSessionViewModel.swift */; }; + 84D184F12A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */; }; + 84D184F42A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */; }; + 84D184F62A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */; }; + 84D184F82A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F72A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift */; }; 84D1ABD827E0A0600073C631 /* AssetStorageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D1ABD727E0A0600073C631 /* AssetStorageInfo.swift */; }; 84D1ABDA27E0ACFA0073C631 /* WalletRemoteSubscriptionWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D1ABD927E0ACFA0073C631 /* WalletRemoteSubscriptionWrapper.swift */; }; 84D1ABDC27E1B4FE0073C631 /* ChainAssetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D1ABDB27E1B4FE0073C631 /* ChainAssetViewModel.swift */; }; @@ -3162,6 +3173,7 @@ CE773CEC15A83AA6D0B404B8 /* DAppListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2BB29AE8E556E6756A4F02 /* DAppListViewController.swift */; }; CEED39FF1C586C00B56B1F0C /* AddDelegationViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74FF4FE525ADD8E7805481E /* AddDelegationViewLayout.swift */; }; CFE7FE8F69D10E2B9E8DA791 /* GovernanceUnavailableTracksViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0BF8DFAA80B405D4A5D891 /* GovernanceUnavailableTracksViewFactory.swift */; }; + D0AD3C44BBFD6A9F9FDEC933 /* WalletConnectSessionDetailsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C09D09E9ED7911D0BF763184 /* WalletConnectSessionDetailsWireframe.swift */; }; D1487642F422F6B96216B0D0 /* GovernanceYourDelegationsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA5A50D493EABD48128286 /* GovernanceYourDelegationsViewFactory.swift */; }; D1C4208A89633395AF2FDB74 /* ReferendumVoteSetupViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3105383F2825940D0105D5 /* ReferendumVoteSetupViewLayout.swift */; }; D1C6EABB48DC3EE254E5A095 /* CrowdloanContributionConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F5B57A24265C36A5F19B78 /* CrowdloanContributionConfirmPresenter.swift */; }; @@ -3218,6 +3230,7 @@ DECE047E7BBE2B9251A09353 /* ParaStkUnstakeConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6060CE86A7EA49AD05329C /* ParaStkUnstakeConfirmViewController.swift */; }; DEF53463C2C780D702E9C2CA /* ParaStkSelectCollatorsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AB8804B896E72AC81879C6 /* ParaStkSelectCollatorsPresenter.swift */; }; E04E3ABA985AA3D89AE20BF5 /* CrowdloanYourContributionsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042E8DD6E5F5FD65361A7BDD /* CrowdloanYourContributionsProtocols.swift */; }; + E06F3BD43E589BCE3904BBCB /* WalletConnectSessionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC122CEA0D33584669126731 /* WalletConnectSessionDetailsViewController.swift */; }; E0CBD1D747361D121555FD51 /* ParaStkRedeemViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEEFB03BBA45F5143484398 /* ParaStkRedeemViewFactory.swift */; }; E14F809C3917EFA4B5388AC8 /* AccountConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A14CA4551FCC2EBD078E2242 /* AccountConfirmViewFactory.swift */; }; E221892A5B6A41A944B88336 /* ParaStkYieldBoostStartProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50829CD47D3F60E3067418B4 /* ParaStkYieldBoostStartProtocols.swift */; }; @@ -3277,6 +3290,7 @@ F1BED07F67119E1BD052952A /* ReferendumVoteSetupWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2C456A80FA66E6F140814 /* ReferendumVoteSetupWireframe.swift */; }; F20C8D17ABF18B7104E14394 /* StakingAmountInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312DE7ADA5ABC3214AD3D4AD /* StakingAmountInteractor.swift */; }; F27AAD7BC84793FA63027F8C /* AssetsSettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA134C6DB56DE1DFBA1B88B4 /* AssetsSettingsInteractor.swift */; }; + F332FA8C330A16C3894B6542 /* WalletConnectSessionDetailsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA94E5B74194AABA482F2FD /* WalletConnectSessionDetailsProtocols.swift */; }; F35B520D7955A70588AB593C /* ReferendumVoteConfirmWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C784EDFB9227884E30FBE6E /* ReferendumVoteConfirmWireframe.swift */; }; F382BF4F8C3C46C7C21DE5C0 /* ParaStkUnstakeConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86DCB6F3977BDE1BDC7BC3F9 /* ParaStkUnstakeConfirmPresenter.swift */; }; F3BB50CCA38C9B47FDBEDF53 /* ReferendumVotersInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23CC3812E4DFC26484324D57 /* ReferendumVotersInteractor.swift */; }; @@ -3403,6 +3417,7 @@ F85F1BCAD47F0596FBFBA110 /* ParaStkRebondWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2B583DB30C6C818B0F952D /* ParaStkRebondWireframe.swift */; }; F88D85C73094F6A1FC494D87 /* DAppSearchWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A191B92AD171FDDDD8C30E2 /* DAppSearchWireframe.swift */; }; F8C0CA3DDBCB5E509295F099 /* MarkdownDescriptionViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF9ED27CF12B7DA8B1378CF /* MarkdownDescriptionViewFactory.swift */; }; + F92E73C24AB577F37B35649E /* WalletConnectSessionDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14339E7BE9FE045C6A2AB52 /* WalletConnectSessionDetailsInteractor.swift */; }; FA62AACACA15CB04275DE957 /* ParaStkYourCollatorsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21A2FD02269432066884F5AF /* ParaStkYourCollatorsInteractor.swift */; }; FA894DFA8EEBB0B4562CD788 /* LedgerAccountConfirmationViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392B5AA43C68E640C9FDEE04 /* LedgerAccountConfirmationViewFactory.swift */; }; FB405A41F9B89097016D4C78 /* ChainAddressDetailsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 245DED717B5B3FC380C24E3D /* ChainAddressDetailsWireframe.swift */; }; @@ -3414,6 +3429,7 @@ FF2E6053091276EBBA4D986C /* LedgerAccountConfirmationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902360B8577FAC9504F5854F /* LedgerAccountConfirmationPresenter.swift */; }; FF985AA0ABA33118AC02A761 /* GovernanceEditDelegationTracksViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88D7A2C9F9079EFF63E378B /* GovernanceEditDelegationTracksViewFactory.swift */; }; FFE19A19E5B4ED67A2C61951 /* DAppSearchProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F96182151D003DF6789CB4B /* DAppSearchProtocols.swift */; }; + FFF7CC228C1EB14B8677BD17 /* WalletConnectSessionDetailsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4D4C049E3B32A529DDFEB5 /* WalletConnectSessionDetailsViewLayout.swift */; }; FFFA51179A4E22E457BF6F78 /* AddDelegationWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D779CD7E426E469760290DBC /* AddDelegationWireframe.swift */; }; /* End PBXBuildFile section */ @@ -3475,6 +3491,7 @@ 0D6E67AD564867E121601F18 /* WalletsListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsListPresenter.swift; sourceTree = ""; }; 0D9C85AB0C9D53B522DCF3C5 /* CreateWatchOnlyInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CreateWatchOnlyInteractor.swift; sourceTree = ""; }; 0EC18369BDAF9076681B6E3F /* InAppUpdatesWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InAppUpdatesWireframe.swift; sourceTree = ""; }; + 0FA94E5B74194AABA482F2FD /* WalletConnectSessionDetailsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsProtocols.swift; sourceTree = ""; }; 1039AA3654461114FBB86844 /* DAppTxDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppTxDetailsPresenter.swift; sourceTree = ""; }; 10E27EB6FF31F9D247DEFABB /* ParaStkStakeConfirmViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeConfirmViewLayout.swift; sourceTree = ""; }; 10F3CCA5BA57C68D5AE2B42F /* DAppListProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppListProtocols.swift; sourceTree = ""; }; @@ -3808,6 +3825,7 @@ 748E0AF1A286016CB220155C /* ControllerAccountInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountInteractor.swift; sourceTree = ""; }; 750C8C7940C944DA4C8CB95F /* DAppListInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppListInteractor.swift; sourceTree = ""; }; 75247AFBEFD8347011098F69 /* GovernanceRemoveVotesConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceRemoveVotesConfirmViewFactory.swift; sourceTree = ""; }; + 75ADB95DAB1F29E6A3FDD166 /* WalletConnectSessionDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsPresenter.swift; sourceTree = ""; }; 75CFAA1D2D04553B10421C69 /* DAppAuthConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAuthConfirmProtocols.swift; sourceTree = ""; }; 76AA6A6232B1CF2D5AF74D0D /* ParaStkUnstakeInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkUnstakeInteractor.swift; sourceTree = ""; }; 781FA4C896AF31B4035AFB38 /* ChainAddressDetailsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsViewFactory.swift; sourceTree = ""; }; @@ -4536,6 +4554,7 @@ 84592F4B298716540001BB56 /* GovernanceOffchainVotingFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainVotingFactory.swift; sourceTree = ""; }; 84592F4E298716E80001BB56 /* GovernanceOffchainVoting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainVoting.swift; sourceTree = ""; }; 84592F502987B2E80001BB56 /* SubqueryVotingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubqueryVotingResponse.swift; sourceTree = ""; }; + 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsInteractorError.swift; sourceTree = ""; }; 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcalaContributionSource.swift; sourceTree = ""; }; 8459A9C92746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanOffchainSubscriber.swift; sourceTree = ""; }; 8459A9CB2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanOffchainSubscriptionHandler.swift; sourceTree = ""; }; @@ -5670,6 +5689,14 @@ 84D17EDB28054C9C00F7BAFF /* DAppLocalSubscriptionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppLocalSubscriptionHandler.swift; sourceTree = ""; }; 84D17EDD28054CC400F7BAFF /* DAppLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppLocalSubscriptionFactory.swift; sourceTree = ""; }; 84D17EE02805A62600F7BAFF /* DAppAlertPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAlertPresentable.swift; sourceTree = ""; }; + 84D184E72A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableContainerLayoutView.swift; sourceTree = ""; }; + 84D184E92A04D9980060C1BD /* StackStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackStatusCell.swift; sourceTree = ""; }; + 84D184EB2A04DA810060C1BD /* GlowingStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlowingStatusView.swift; sourceTree = ""; }; + 84D184EE2A04E2760060C1BD /* WalletConnectSessionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionViewModel.swift; sourceTree = ""; }; + 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectNetworksViewModel.swift; sourceTree = ""; }; + 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackInfoTableCell+WallectConnect.swift"; sourceTree = ""; }; + 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackStatusCell+WalletConnect.swift"; sourceTree = ""; }; + 84D184F72A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionViewModelFactory.swift; sourceTree = ""; }; 84D1ABD727E0A0600073C631 /* AssetStorageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetStorageInfo.swift; sourceTree = ""; }; 84D1ABD927E0ACFA0073C631 /* WalletRemoteSubscriptionWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletRemoteSubscriptionWrapper.swift; sourceTree = ""; }; 84D1ABDB27E1B4FE0073C631 /* ChainAssetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAssetViewModel.swift; sourceTree = ""; }; @@ -6564,6 +6591,7 @@ AFC1FCEA168E40235A1D3EA6 /* GovernanceDelegateSearchViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateSearchViewController.swift; sourceTree = ""; }; B03974ECD8AEF39FDCA277D7 /* LedgerNetworkSelectionViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerNetworkSelectionViewFactory.swift; sourceTree = ""; }; B0B2C32E11E2F7F3D4A1D3AB /* ParaStkStakeSetupWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupWireframe.swift; sourceTree = ""; }; + B14339E7BE9FE045C6A2AB52 /* WalletConnectSessionDetailsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsInteractor.swift; sourceTree = ""; }; B14E9691A7628B66958F8744 /* LedgerInstructionsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsProtocols.swift; sourceTree = ""; }; B18E8361691E548ABAB33EA4 /* GovernanceEditDelegationTracksViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceEditDelegationTracksViewController.swift; sourceTree = ""; }; B193A261FDF933FE6C874B4E /* WalletConnectServiceFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectServiceFactory.swift; sourceTree = ""; }; @@ -6588,6 +6616,7 @@ B8B263D5668F1C91E2CF61D9 /* GovernanceRevokeDelegationConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceRevokeDelegationConfirmWireframe.swift; sourceTree = ""; }; B9035D0F739C88CCBE11E886 /* GovernanceRemoveVotesConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceRemoveVotesConfirmInteractor.swift; sourceTree = ""; }; B90CEC70F101AA25A4C00021 /* YourValidatorListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourValidatorListViewController.swift; sourceTree = ""; }; + BA4D4C049E3B32A529DDFEB5 /* WalletConnectSessionDetailsViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsViewLayout.swift; sourceTree = ""; }; BA518E1D79D86360F145B428 /* TokensAddSelectNetworkInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensAddSelectNetworkInteractor.swift; sourceTree = ""; }; BAB2478DE3AF0885A3ED7ED8 /* StakingRedeemPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRedeemPresenter.swift; sourceTree = ""; }; BAF9ED27CF12B7DA8B1378CF /* MarkdownDescriptionViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MarkdownDescriptionViewFactory.swift; sourceTree = ""; }; @@ -6599,8 +6628,10 @@ BD70B47DF7F9DE75CE7AE33F /* ParitySignerTxQrProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParitySignerTxQrProtocols.swift; sourceTree = ""; }; BDB66DA5010441586327E139 /* MoonbeamTermsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoonbeamTermsViewController.swift; sourceTree = ""; }; BE0F7D4389B932A1F2E17361 /* DAppSearchViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSearchViewLayout.swift; sourceTree = ""; }; + BE103341935B2A4B8C32B966 /* WalletConnectSessionDetailsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsViewFactory.swift; sourceTree = ""; }; BF16BF87D3DEA31E6003C969 /* DelegationListWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationListWireframe.swift; sourceTree = ""; }; BF2A45FEEA61BDB78BFAB0D7 /* DelegationReferendumVotersViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationReferendumVotersViewController.swift; sourceTree = ""; }; + C09D09E9ED7911D0BF763184 /* WalletConnectSessionDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsWireframe.swift; sourceTree = ""; }; C0A2F34329579E12A2836E77 /* MessageSheetViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MessageSheetViewController.swift; sourceTree = ""; }; C191A3875F3255B72E01FA92 /* CrowdloanListWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanListWireframe.swift; sourceTree = ""; }; C197897ED8B6E5547C64CD2D /* GovernanceDelegateSearchViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateSearchViewFactory.swift; sourceTree = ""; }; @@ -6874,6 +6905,7 @@ FA59CE2C7AE548ACA9D66FD7 /* CrowdloanContributionConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanContributionConfirmWireframe.swift; sourceTree = ""; }; FB2EA5D52F51F03FBAB490FE /* ChainAddressDetailsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsViewController.swift; sourceTree = ""; }; FBFDF844248CD43AAD13139F /* StakingPayoutConfirmationInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingPayoutConfirmationInteractor.swift; sourceTree = ""; }; + FC122CEA0D33584669126731 /* WalletConnectSessionDetailsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsViewController.swift; sourceTree = ""; }; FC224725C0C1743FDFED5F6E /* DelegationListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DelegationListPresenter.swift; sourceTree = ""; }; FCA43AABAC7555C2E648442F /* DAppSettingsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSettingsViewFactory.swift; sourceTree = ""; }; FE0641B2354E6F236CB9A132 /* NftDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftDetailsPresenter.swift; sourceTree = ""; }; @@ -7127,9 +7159,11 @@ 8413C4A72A025AB0001E190A /* Protocols */, 84E8BA2D2A02207300FD9F40 /* Service */, EBA3D6C1965795AF75E2A18B /* Sessions */, + 7F067CADB4CD05233424BD6D /* SessionDetails */, 84B8AA7729F9049900347A37 /* States */, 84B8AA7629F9048F00347A37 /* Transport */, 849F33B529F7B225001AEFA4 /* Model */, + 84D184F22A04EF740060C1BD /* ViewModel */, ); path = WalletConnect; sourceTree = ""; @@ -7878,6 +7912,22 @@ path = ParaStkRebond; sourceTree = ""; }; + 7F067CADB4CD05233424BD6D /* SessionDetails */ = { + isa = PBXGroup; + children = ( + 84D184ED2A04E25D0060C1BD /* ViewModel */, + 0FA94E5B74194AABA482F2FD /* WalletConnectSessionDetailsProtocols.swift */, + C09D09E9ED7911D0BF763184 /* WalletConnectSessionDetailsWireframe.swift */, + 75ADB95DAB1F29E6A3FDD166 /* WalletConnectSessionDetailsPresenter.swift */, + B14339E7BE9FE045C6A2AB52 /* WalletConnectSessionDetailsInteractor.swift */, + FC122CEA0D33584669126731 /* WalletConnectSessionDetailsViewController.swift */, + BA4D4C049E3B32A529DDFEB5 /* WalletConnectSessionDetailsViewLayout.swift */, + BE103341935B2A4B8C32B966 /* WalletConnectSessionDetailsViewFactory.swift */, + 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */, + ); + path = SessionDetails; + sourceTree = ""; + }; 7F32EC2D6AEE8391CDDDBFA8 /* SelectValidatorsStart */ = { isa = PBXGroup; children = ( @@ -10049,6 +10099,7 @@ 847012652982AE5700F29C87 /* StackTableView+Cell.swift */, 849D14C92994D9BC0048E947 /* StackIconTitleValueCell.swift */, 843125CC299A71B20063745B /* StackButtonsCell.swift */, + 84D184E92A04D9980060C1BD /* StackStatusCell.swift */, ); path = StackTable; sourceTree = ""; @@ -11253,6 +11304,7 @@ 8490149D24AA7F9A008F705E /* View */ = { isa = PBXGroup; children = ( + 84D184E62A04D5880060C1BD /* ScrollableContainerView */, 847012642982ADCF00F29C87 /* StackTable */, 84C2063628D1BEBD006D0D52 /* AccountDetailsSelection */, 8422F2EC2887E3B100C7B840 /* TextInputView */, @@ -11275,7 +11327,6 @@ 8460E11C2542011200826F55 /* DetailsTriangularedView.swift */, 8460E11E25420A2C00826F55 /* DetailsTriangularedView+Inspectable.swift */, 847C963425534E41002D288F /* UIFactory.swift */, - 847C963C255351BB002D288F /* ScrollableContainerView.swift */, 8443FDB02554B7640092893D /* TitledMnemonicView.swift */, 84644AC42567EC05004EAA4B /* MultilineTriangularedView.swift */, 846CDECC258D212D009F3E75 /* AlertImageWithTitleView.swift */, @@ -11345,6 +11396,7 @@ 845AADA02902D02400B5AE96 /* TitleValueDiffView.swift */, 84BAD20C293A1B6E00C55C49 /* TopCustomSearchView.swift */, 8480154D29BB6521008557F3 /* BorderedImageView.swift */, + 84D184EB2A04DA810060C1BD /* GlowingStatusView.swift */, ); path = View; sourceTree = ""; @@ -13442,6 +13494,34 @@ path = Protocols; sourceTree = ""; }; + 84D184E62A04D5880060C1BD /* ScrollableContainerView */ = { + isa = PBXGroup; + children = ( + 847C963C255351BB002D288F /* ScrollableContainerView.swift */, + 84D184E72A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift */, + ); + path = ScrollableContainerView; + sourceTree = ""; + }; + 84D184ED2A04E25D0060C1BD /* ViewModel */ = { + isa = PBXGroup; + children = ( + 84D184EE2A04E2760060C1BD /* WalletConnectSessionViewModel.swift */, + 84D184F72A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 84D184F22A04EF740060C1BD /* ViewModel */ = { + isa = PBXGroup; + children = ( + 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */, + 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */, + 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 84D2F19E27730E0F0040C680 /* ViewModel */ = { isa = PBXGroup; children = ( @@ -17533,6 +17613,7 @@ 84AE7ABD27D55E3400495267 /* NftDetailsProgress.swift in Sources */, 8470D6D0253E321C009E9A5D /* StorageSubscriptionProtocols.swift in Sources */, 8499FE6C27BD319300712589 /* RMRKV2OperationFactory.swift in Sources */, + 84D184F42A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift in Sources */, 88A5317D28B9170100AF18F5 /* NSCollectionLayoutSection+create.swift in Sources */, 8444D1BE2671465A00AF6D8C /* CrowdloanBonusServiceError.swift in Sources */, 8448F7A22882ABF50080CEA9 /* CustomSearchView.swift in Sources */, @@ -18195,6 +18276,7 @@ 842A738627DE04F1006EE1EA /* TransactionLocalSubscriptionFactory.swift in Sources */, 8489A6DA27FDC49D0040C066 /* StakingUnbondingsView.swift in Sources */, 846952A42852A1640083E0B4 /* StakingDuration.swift in Sources */, + 84D184EC2A04DA810060C1BD /* GlowingStatusView.swift in Sources */, 8428765F24ADE0BB00D91AD8 /* UserSettings.swift in Sources */, 84FAB0652542CA4200319F74 /* CDContactItem+CoreDataDecodable.swift in Sources */, 8472974D260A9CDF009B86D0 /* SelectValidatorsConfirmationModel.swift in Sources */, @@ -18256,6 +18338,7 @@ 84C1B98B24F55E8200FE5470 /* AccountTableViewCell.swift in Sources */, 8487580927EDEDB200495306 /* RawChainView.swift in Sources */, 8490150F24AB8A3A008F705E /* WalletEmptyStateDataSource+Module.swift in Sources */, + 84D184F82A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift in Sources */, 84CA68E126BEAC7C003B9453 /* SpecVersionSubscriptionFactory.swift in Sources */, 842BDB21278C222100AB4B5A /* DAppBrowserBaseState.swift in Sources */, DAB29F2A9C864D7FCF1AF934 /* UsernameSetupProtocols.swift in Sources */, @@ -18926,6 +19009,7 @@ 84FB9E1A285C57D900B42FC0 /* XcmVersionedMultilocation.swift in Sources */, 849ABE6826278A4100011A2A /* NominateMapper.swift in Sources */, 2C3124A5EBC1AD57C01EEA17 /* SelectValidatorsStartInteractor.swift in Sources */, + 84D184F62A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift in Sources */, 8437F7C3292506D000DB6366 /* EvmSubscriptionMessageFactory.swift in Sources */, 88A5318028B9328E00AF18F5 /* YourWalletsViewSectionModel.swift in Sources */, 842EBB312890A76A00B952D8 /* WalletSelectionViewController.swift in Sources */, @@ -18962,6 +19046,7 @@ B071927DF8DD5C3CA84494BA /* RecommendedValidatorListViewController.swift in Sources */, D6511F7C3E55197F82AB552C /* RecommendedValidatorListViewFactory.swift in Sources */, C7D77690E10875CF1856EBA1 /* StakingRewardPayoutsProtocols.swift in Sources */, + 84D184F12A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift in Sources */, 88A0E0FF28A284C700A9C940 /* SelectedCurrencyDepending.swift in Sources */, 2918DCAEB91F8276446873C8 /* StakingRewardPayoutsWireframe.swift in Sources */, AB5E2A2B4CC823E6F6515ADD /* StakingRewardPayoutsPresenter.swift in Sources */, @@ -19402,6 +19487,7 @@ 84953F6A2934C9E20033F47D /* EtherscanERC20OperationFactory.swift in Sources */, CD9359A2720F2EE1D4E09DF6 /* DAppTxDetailsWireframe.swift in Sources */, 0E364B6F05D390069D049CC2 /* DAppTxDetailsPresenter.swift in Sources */, + 84D184EF2A04E2760060C1BD /* WalletConnectSessionViewModel.swift in Sources */, 9BADFCBF3AF5186094DB8D67 /* DAppTxDetailsInteractor.swift in Sources */, B409644ED1E20062A3EA0316 /* DAppTxDetailsViewController.swift in Sources */, DAD46B2B29A446C19A6ABF2D /* DAppTxDetailsViewLayout.swift in Sources */, @@ -19705,6 +19791,7 @@ 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */, 821518375113295E41E0481C /* ParitySignerTxQrViewFactory.swift in Sources */, 641D7CF89F37B1890516015E /* ParitySignerTxScanProtocols.swift in Sources */, + 84D184E82A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift in Sources */, 1EE4FBB79EE6015D7D3EBDC1 /* ParitySignerTxScanWireframe.swift in Sources */, 887AFC8728BC95F0002A0422 /* MetaAccountChainResponse.swift in Sources */, 042799797DF7E6FD02D1D1E6 /* ParitySignerTxScanPresenter.swift in Sources */, @@ -19776,6 +19863,7 @@ 84E8BA0729FE888C00FD9F40 /* DAppUnknownChain.swift in Sources */, 7E5ACF8DDF17C054E6E1B3D5 /* ParaStkYieldBoostSetupWireframe.swift in Sources */, 7C0135CA49EF6B535030643E /* ParaStkYieldBoostSetupPresenter.swift in Sources */, + 84D184EA2A04D9980060C1BD /* StackStatusCell.swift in Sources */, 21B297239CC294307EF20B58 /* ParaStkYieldBoostSetupInteractor.swift in Sources */, AE180C8B30831C9BAA39763A /* ParaStkYieldBoostSetupViewController.swift in Sources */, 8824D4222902D92F0022D778 /* ReferendumFullDetailsInteractor.swift in Sources */, @@ -19842,6 +19930,7 @@ 879D493C025963619CFADF4F /* GovernanceUnlockSetupProtocols.swift in Sources */, 46298240F3528B5C62AEC29E /* GovernanceUnlockSetupWireframe.swift in Sources */, 9097EE6D11E2E023D2637BE5 /* GovernanceUnlockSetupPresenter.swift in Sources */, + 845938842A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift in Sources */, 38D0977931828C7894579968 /* GovernanceUnlockSetupInteractor.swift in Sources */, 16098DABB1C9C058C1965F1D /* GovernanceUnlockSetupViewController.swift in Sources */, 8493FF38291A59D800F09F1B /* ReferendumMetadataMapper.swift in Sources */, @@ -20033,6 +20122,13 @@ D8CB6639857FE917719EF0AF /* WalletConnectSessionsViewController.swift in Sources */, F06129946DFEC9B7F282EB46 /* WalletConnectSessionsViewLayout.swift in Sources */, 6DC454C4BA27C98987F5DC52 /* WalletConnectSessionsViewFactory.swift in Sources */, + F332FA8C330A16C3894B6542 /* WalletConnectSessionDetailsProtocols.swift in Sources */, + D0AD3C44BBFD6A9F9FDEC933 /* WalletConnectSessionDetailsWireframe.swift in Sources */, + 0A44D28DF4BCF56131752F35 /* WalletConnectSessionDetailsPresenter.swift in Sources */, + F92E73C24AB577F37B35649E /* WalletConnectSessionDetailsInteractor.swift in Sources */, + E06F3BD43E589BCE3904BBCB /* WalletConnectSessionDetailsViewController.swift in Sources */, + FFF7CC228C1EB14B8677BD17 /* WalletConnectSessionDetailsViewLayout.swift in Sources */, + 54983C354F7EDCD8014C8371 /* WalletConnectSessionDetailsViewFactory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift index bf01221d2e..4c78ebe18d 100644 --- a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift +++ b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift @@ -65,4 +65,9 @@ extension UILabel.Style { textColor: R.color.colorTextPrimary()!, font: .semiBoldBody ) + + static let title3Primary = UILabel.Style( + textColor: R.color.colorTextPrimary()!, + font: .semiBoldTitle3 + ) } diff --git a/novawallet/Common/Extension/UIKit/TriangularedButton+Style.swift b/novawallet/Common/Extension/UIKit/TriangularedButton+Style.swift index 71882ae261..bd9daeb0c7 100644 --- a/novawallet/Common/Extension/UIKit/TriangularedButton+Style.swift +++ b/novawallet/Common/Extension/UIKit/TriangularedButton+Style.swift @@ -12,6 +12,11 @@ extension TriangularedButton { applySecondaryEnabledStyle() } + func applyDestructiveDefaultStyle() { + imageWithTitleView?.titleFont = .semiBoldSubheadline + applyDestructiveEnabledStyle() + } + func applyAccessoryStyle() { triangularedView?.shadowOpacity = 0.0 triangularedView?.fillColor = .clear @@ -50,6 +55,18 @@ extension TriangularedButton { changesContentOpacityWhenHighlighted = true } + func applyDestructiveEnabledStyle() { + triangularedView?.shadowOpacity = 0.0 + triangularedView?.fillColor = R.color.colorButtonBackgroundReject()! + triangularedView?.highlightedFillColor = R.color.colorButtonBackgroundReject()! + triangularedView?.strokeColor = .clear + triangularedView?.highlightedStrokeColor = .clear + + imageWithTitleView?.titleColor = R.color.colorButtonText()! + + changesContentOpacityWhenHighlighted = true + } + func applyDisabledStyle() { triangularedView?.shadowOpacity = 0.0 triangularedView?.fillColor = R.color.colorButtonBackgroundInactive()! diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index b6693f25fa..9f38dd9ab4 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -5,7 +5,7 @@ import Combine protocol WalletConnectServiceDelegate: AnyObject { func walletConnect(service: WalletConnectServiceProtocol, proposal: Session.Proposal) - func walletConnect(service: WalletConnectServiceProtocol, establishedSession: Session) + func walletConnect(service: WalletConnectServiceProtocol, didChange sessions: [Session]) func walletConnect(service: WalletConnectServiceProtocol, request: Request, session: Session?) func walletConnect(service: WalletConnectServiceProtocol, error: WalletConnectServiceError) } @@ -19,6 +19,8 @@ protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { func submit(signingDecision: WalletConnectSignDecision) func getSessions() -> [Session] + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) } enum WalletConnectServiceError: Error { @@ -26,6 +28,7 @@ enum WalletConnectServiceError: Error { case connectFailed(uri: String, internalError: Error) case proposalFailed(decision: WalletConnectProposalDecision, internalError: Error) case signFailed(decision: WalletConnectSignDecision, internalError: Error) + case disconnectionFailed(sessionId: String, internalError: Error) } final class WalletConnectService { @@ -64,14 +67,14 @@ final class WalletConnectService { strongSelf.delegate?.walletConnect(service: strongSelf, proposal: proposal) } - sessionCancellable = client?.sessionSettlePublisher + sessionCancellable = client?.sessionsPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] session in + .sink { [weak self] sessions in guard let strongSelf = self else { return } - strongSelf.delegate?.walletConnect(service: strongSelf, establishedSession: session) + strongSelf.delegate?.walletConnect(service: strongSelf, didChange: sessions) } requestCancellable = client?.sessionRequestPublisher @@ -244,6 +247,27 @@ extension WalletConnectService: WalletConnectServiceProtocol { clearClient() clearSubscriptions() } + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) { + Task { + do { + try await client?.disconnect(topic: session) + + DispatchQueue.main.async { + completion(nil) + } + + } catch { + logger.error("Disconnecting \(session) failed: \(error)") + + notify(error: .disconnectionFailed(sessionId: session, internalError: error)) + + DispatchQueue.main.async { + completion(error) + } + } + } + } } private final class DefaultWebSocket: WebSocket, WebSocketConnecting { diff --git a/novawallet/Common/View/GlowingStatusView.swift b/novawallet/Common/View/GlowingStatusView.swift new file mode 100644 index 0000000000..23928f05f0 --- /dev/null +++ b/novawallet/Common/View/GlowingStatusView.swift @@ -0,0 +1,47 @@ +import UIKit + +final class GlowingStatusView: GenericPairValueView { + var indicator: GlowingView { fView } + var titleLabel: UILabel { sView } + + override init(frame: CGRect) { + super.init(frame: frame) + + configure() + } + + private func configure() { + setHorizontalAndSpacing(8) + + titleLabel.apply(style: .footnotePrimary) + } +} + +extension GlowingStatusView { + struct Style { + let backgroundColor: UIColor + let mainColor: UIColor + + static var active: Style { + let color = R.color.colorTextPositive()! + return .init( + backgroundColor: color.withAlphaComponent(0.4), + mainColor: color + ) + } + + static var inactive: Style { + let color = R.color.colorTextNegative()! + return .init( + backgroundColor: color.withAlphaComponent(0.4), + mainColor: color + ) + } + } + + func apply(style: Style) { + indicator.outerFillColor = style.backgroundColor + indicator.innerFillColor = style.mainColor + titleLabel.textColor = style.mainColor + } +} diff --git a/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift b/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift new file mode 100644 index 0000000000..c2e84ace92 --- /dev/null +++ b/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift @@ -0,0 +1,63 @@ +import UIKit + +class ScrollableContainerLayoutView: UIView { + let containerView: ScrollableContainerView = { + let view = ScrollableContainerView(axis: .vertical, respectsSafeArea: true) + view.stackView.layoutMargins = UIEdgeInsets(top: 16, left: 16.0, bottom: 0.0, right: 16.0) + view.stackView.isLayoutMarginsRelativeArrangement = true + view.stackView.alignment = .fill + return view + }() + + var stackView: UIStackView { containerView.stackView } + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + setupLayout() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupStyle() { + backgroundColor = R.color.colorSecondaryScreenBackground() + } + + func setupLayout() { + addSubview(containerView) + containerView.snp.makeConstraints { make in + make.top.leading.trailing.bottom.equalToSuperview() + } + } + + func addArrangedSubview(_ view: UIView, spacingAfter value: CGFloat = 0) { + stackView.addArrangedSubview(view) + + if value > 0 { + stackView.setCustomSpacing(value, after: view) + } + } +} + +class ScrollableContainerActionLayoutView: ScrollableContainerLayoutView { + let actionLoadableView = LoadableActionView() + + override func setupLayout() { + addSubview(actionLoadableView) + actionLoadableView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) + make.bottom.equalTo(safeAreaLayoutGuide).inset(UIConstants.actionBottomInset) + make.height.equalTo(UIConstants.actionHeight) + } + + addSubview(containerView) + containerView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.bottom.equalTo(actionLoadableView.snp.top).offset(-8.0) + } + } +} diff --git a/novawallet/Common/View/ScrollableContainerView.swift b/novawallet/Common/View/ScrollableContainerView/ScrollableContainerView.swift similarity index 100% rename from novawallet/Common/View/ScrollableContainerView.swift rename to novawallet/Common/View/ScrollableContainerView/ScrollableContainerView.swift diff --git a/novawallet/Common/View/StackTable/StackStatusCell.swift b/novawallet/Common/View/StackTable/StackStatusCell.swift new file mode 100644 index 0000000000..03e3bf499a --- /dev/null +++ b/novawallet/Common/View/StackTable/StackStatusCell.swift @@ -0,0 +1,34 @@ +import UIKit + +final class StackStatusCell: RowView> { + var titleLabel: UILabel { rowContentView.titleView } + var statusView: GlowingStatusView { rowContentView.valueView } + + convenience init() { + self.init(frame: CGRect(origin: .zero, size: CGSize(width: 340, height: 44.0))) + } + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .clear + + configure() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configure() { + isUserInteractionEnabled = false + + titleLabel.textColor = R.color.colorTextSecondary() + titleLabel.font = .regularFootnote + + statusView.apply(style: .active) + } +} + +extension StackStatusCell: StackTableViewCellProtocol {} diff --git a/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift b/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift index 042070156f..a57e1f8750 100644 --- a/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift +++ b/novawallet/Modules/DApp/DAppAuthSettings/View/DAppAuthSettingsTableCell.swift @@ -5,17 +5,6 @@ protocol DAppAuthSettingsTableCellDelegate: AnyObject { func authSettingsDidSelectCell(_ cell: DAppAuthSettingsTableCell) } -enum DAppIconCellConstants { - static let size = CGSize(width: 48.0, height: 48.0) - static let insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 6.0) - static var displaySize: CGSize { - CGSize( - width: size.width - insets.left - insets.right, - height: size.height - insets.top - insets.bottom - ) - } -} - final class DAppAuthSettingsTableCell: UITableViewCell { let iconView: DAppIconView = { let view = DAppIconView() diff --git a/novawallet/Modules/DApp/View/DAppIconView.swift b/novawallet/Modules/DApp/View/DAppIconView.swift index 5dd88a26a7..f08e00f4a5 100644 --- a/novawallet/Modules/DApp/View/DAppIconView.swift +++ b/novawallet/Modules/DApp/View/DAppIconView.swift @@ -64,3 +64,25 @@ final class DAppIconView: UIView { } } } + +enum DAppIconCellConstants { + static let size = CGSize(width: 48.0, height: 48.0) + static let insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 6.0) + static var displaySize: CGSize { + CGSize( + width: size.width - insets.left - insets.right, + height: size.height - insets.top - insets.bottom + ) + } +} + +enum DAppIconLargeConstants { + static let size = CGSize(width: 88.0, height: 88.0) + static let insets = UIEdgeInsets(top: 11.0, left: 11.0, bottom: 11.0, right: 11.0) + static var displaySize: CGSize { + CGSize( + width: size.width - insets.left - insets.right, + height: size.height - insets.top - insets.bottom + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift index bf9bb69f4b..e39156830e 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -44,6 +44,10 @@ extension WalletConnectInteractor: WalletConnectDelegateInputProtocol { func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) { transport.fetchSessions(completion) } + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) { + transport.disconnect(from: session, completion: completion) + } } extension WalletConnectInteractor: WalletConnectTransportDelegate { diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift index 6ac02515e9..e1061648fa 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift @@ -11,6 +11,8 @@ protocol WalletConnectDelegateInputProtocol: AnyObject { func getSessionsCount() -> Int func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) } protocol WalletConnectDelegateOutputProtocol: AnyObject { diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift new file mode 100644 index 0000000000..90103f8575 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift @@ -0,0 +1,15 @@ +import Foundation + +struct WalletConnectSessionViewModel { + enum Status { + case active + case expired + } + + let iconViewModel: ImageViewModelProtocol + let title: String + let wallet: DisplayWalletViewModel? + let host: String + let networks: WalletConnectNetworksViewModel + let status: Status +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift new file mode 100644 index 0000000000..fc741d185c --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift @@ -0,0 +1,33 @@ +import Foundation + +protocol WalletConnectSessionViewModelFactoryProtocol { + func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionViewModel +} + +final class WalletConnectSessionViewModelFactory: WalletConnectSessionViewModelFactoryProtocol { + let walletViewModelFactory = WalletAccountViewModelFactory() + + func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionViewModel { + let walletViewModel: DisplayWalletViewModel? = model.wallet.flatMap { wallet in + try? walletViewModelFactory.createDisplayViewModel(from: wallet) + } + + let iconViewModel: ImageViewModelProtocol + + if let icon = model.dAppIcon { + iconViewModel = RemoteImageViewModel(url: icon) + } else { + let icon = R.image.iconDefaultDapp()! + iconViewModel = StaticImageViewModel(image: icon) + } + + return .init( + iconViewModel: iconViewModel, + title: model.dAppName ?? "", + wallet: walletViewModel, + host: model.dAppHost ?? "", + networks: .init(network: nil, supported: 0, unsupported: 0), + status: model.active ? .active : .expired + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift new file mode 100644 index 0000000000..f4c5d80425 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift @@ -0,0 +1,52 @@ +import UIKit + +final class WalletConnectSessionDetailsInteractor { + weak var presenter: WalletConnectSessionDetailsInteractorOutputProtocol? + + let walletConnect: WalletConnectDelegateInputProtocol + let sessionId: String + + init(walletConnect: WalletConnectDelegateInputProtocol, sessionId: String) { + self.walletConnect = walletConnect + self.sessionId = sessionId + } + + private func updateSession(for sessionId: String) { + walletConnect.fetchSessions { [weak self] result in + switch result { + case let .success(sessions): + if let session = sessions.first(where: { $0.sessionId == sessionId }) { + self?.presenter?.didUpdate(session: session) + } else { + self?.presenter?.didDisconnect() + } + case let .failure(error): + self?.presenter?.didReceive(error: .sessionUpdateFailed(error)) + } + } + } +} + +extension WalletConnectSessionDetailsInteractor: WalletConnectSessionDetailsInteractorInputProtocol { + func setup() { + walletConnect.add(delegate: self) + } + + func retrySessionUpdate() { + updateSession(for: sessionId) + } + + func disconnect() { + walletConnect.disconnect(from: sessionId) { [weak self] optError in + if let error = optError { + self?.presenter?.didReceive(error: .disconnectionFailed(error)) + } + } + } +} + +extension WalletConnectSessionDetailsInteractor: WalletConnectDelegateOutputProtocol { + func walletConnectDidChangeSessions() { + updateSession(for: sessionId) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift new file mode 100644 index 0000000000..5c6e611b10 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift @@ -0,0 +1,6 @@ +import Foundation + +enum WalletConnectSessionDetailsInteractorError: Error { + case sessionUpdateFailed(Error) + case disconnectionFailed(Error) +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift new file mode 100644 index 0000000000..3fcc526401 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift @@ -0,0 +1,98 @@ +import Foundation +import SoraFoundation + +final class WalletConnectSessionDetailsPresenter { + weak var view: WalletConnectSessionDetailsViewProtocol? + let wireframe: WalletConnectSessionDetailsWireframeProtocol + let interactor: WalletConnectSessionDetailsInteractorInputProtocol + let viewModelFactory: WalletConnectSessionViewModelFactoryProtocol + + private var session: WalletConnectSession + + let logger: LoggerProtocol + + init( + interactor: WalletConnectSessionDetailsInteractorInputProtocol, + wireframe: WalletConnectSessionDetailsWireframeProtocol, + viewModelFactory: WalletConnectSessionViewModelFactoryProtocol, + session: WalletConnectSession, + localizationManager: LocalizationManagerProtocol, + logger: LoggerProtocol + ) { + self.interactor = interactor + self.wireframe = wireframe + self.viewModelFactory = viewModelFactory + self.session = session + self.logger = logger + self.localizationManager = localizationManager + } + + private func updateView() { + let viewModel = viewModelFactory.createViewModel(from: session) + + view?.didReceive(viewModel: viewModel) + } + + private func performDisconnect() { + view?.didStartLoading() + + interactor.disconnect() + } +} + +extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsPresenterProtocol { + func setup() { + interactor.setup() + } + + func presentNetworks() {} + + func disconnect() { + performDisconnect() + } +} + +extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsInteractorOutputProtocol { + func didUpdate(session: WalletConnectSession) { + self.session = session + + updateView() + } + + func didDisconnect() { + view?.didStopLoading() + + wireframe.close(view: view) + } + + func didReceive(error: WalletConnectSessionDetailsInteractorError) { + logger.error("Did receive error: \(error)") + + switch error { + case .sessionUpdateFailed: + wireframe.presentRequestStatus( + on: view, + locale: selectedLocale + ) { [weak self] in + self?.interactor.retrySessionUpdate() + } + case .disconnectionFailed: + view?.didStopLoading() + + wireframe.presentRequestStatus( + on: view, + locale: selectedLocale + ) { [weak self] in + self?.performDisconnect() + } + } + } +} + +extension WalletConnectSessionDetailsPresenter: Localizable { + func applyLocalization() { + if let view, view.isSetup { + updateView() + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift new file mode 100644 index 0000000000..20e1640d62 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift @@ -0,0 +1,26 @@ +protocol WalletConnectSessionDetailsViewProtocol: ControllerBackedProtocol, LoadableViewProtocol { + func didReceive(viewModel: WalletConnectSessionViewModel) +} + +protocol WalletConnectSessionDetailsPresenterProtocol: AnyObject { + func setup() + func presentNetworks() + func disconnect() +} + +protocol WalletConnectSessionDetailsInteractorInputProtocol: AnyObject { + func setup() + func retrySessionUpdate() + func disconnect() +} + +protocol WalletConnectSessionDetailsInteractorOutputProtocol: AnyObject { + func didUpdate(session: WalletConnectSession) + func didDisconnect() + func didReceive(error: WalletConnectSessionDetailsInteractorError) +} + +protocol WalletConnectSessionDetailsWireframeProtocol: AlertPresentable, ErrorPresentable, + CommonRetryable { + func close(view: WalletConnectSessionDetailsViewProtocol?) +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift new file mode 100644 index 0000000000..ce0b0d4268 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift @@ -0,0 +1,104 @@ +import UIKit +import SoraFoundation + +final class WalletConnectSessionViewController: UIViewController, ViewHolder { + typealias RootViewType = WalletConnectSessionDetailsViewLayout + + let presenter: WalletConnectSessionDetailsPresenterProtocol + + init( + presenter: WalletConnectSessionDetailsPresenterProtocol, + localizationManager: LocalizationManagerProtocol + ) { + self.presenter = presenter + super.init(nibName: nil, bundle: nil) + + self.localizationManager = localizationManager + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = WalletConnectSessionDetailsViewLayout() + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupLocalization() + + presenter.setup() + } + + private func setupLocalization() { + let languages = selectedLocale.rLanguages + + title = R.string.localizable.commonWalletConnect( + preferredLanguages: languages + ) + + rootView.walletCell.titleLabel.text = R.string.localizable.commonWallet( + preferredLanguages: languages + ) + + rootView.dappCell.titleLabel.text = R.string.localizable.commonDapp( + preferredLanguages: languages + ) + + rootView.statusCell.titleLabel.text = R.string.localizable.commonStatus( + preferredLanguages: languages + ) + + rootView.actionLoadableView.actionButton.imageWithTitleView?.title = R.string.localizable + .commonDisconnect(preferredLanguages: languages) + } + + private func setupHandlers() { + rootView.networksCell.addTarget( + self, + action: #selector(actionNetworks), + for: .touchUpInside + ) + + rootView.actionLoadableView.actionButton.addTarget( + self, + action: #selector(actionDisconnect), + for: .touchUpInside + ) + } + + @objc func actionNetworks() { + presenter.presentNetworks() + } + + @objc func actionDisconnect() { + presenter.disconnect() + } +} + +extension WalletConnectSessionViewController: WalletConnectSessionDetailsViewProtocol { + func didReceive(viewModel: WalletConnectSessionViewModel) { + rootView.bind(viewModel: viewModel, locale: selectedLocale) + } +} + +extension WalletConnectSessionViewController: LoadableViewProtocol { + func didStartLoading() { + rootView.actionLoadableView.startLoading() + } + + func didStopLoading() { + rootView.actionLoadableView.stopLoading() + } +} + +extension WalletConnectSessionViewController: Localizable { + func applyLocalization() { + if isViewLoaded { + setupLocalization() + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewFactory.swift new file mode 100644 index 0000000000..62062bedd0 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewFactory.swift @@ -0,0 +1,50 @@ +import Foundation +import SoraFoundation + +struct WalletConnectSessionDetailsViewFactory { + static func createView( + for session: WalletConnectSession, + dappMediator: DAppInteractionMediating + ) -> WalletConnectSessionDetailsViewProtocol? { + guard let interactor = createInteractor(for: session, dappMediator: dappMediator) else { + return nil + } + + let wireframe = WalletConnectSessionDetailsWireframe() + + let localizationManager = LocalizationManager.shared + + let presenter = WalletConnectSessionDetailsPresenter( + interactor: interactor, + wireframe: wireframe, + viewModelFactory: WalletConnectSessionViewModelFactory(), + session: session, + localizationManager: localizationManager, + logger: Logger.shared + ) + + let view = WalletConnectSessionViewController( + presenter: presenter, + localizationManager: localizationManager + ) + + presenter.view = view + interactor.presenter = presenter + + return view + } + + static func createInteractor( + for session: WalletConnectSession, + dappMediator: DAppInteractionMediating + ) -> WalletConnectSessionDetailsInteractor? { + guard + let walletConnect = dappMediator.children.first( + where: { $0 is WalletConnectDelegateInputProtocol } + ) as? WalletConnectDelegateInputProtocol else { + return nil + } + + return .init(walletConnect: walletConnect, sessionId: session.sessionId) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift new file mode 100644 index 0000000000..e36c9de5b2 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift @@ -0,0 +1,56 @@ +import UIKit + +final class WalletConnectSessionDetailsViewLayout: ScrollableContainerActionLayoutView { + let titleView: GenericPairValueView = .create { view in + view.setVerticalAndSpacing(24) + view.stackView.alignment = .center + view.sView.apply(style: .secondaryScreenTitle) + view.sView.numberOfLines = 0 + view.fView.contentInsets = DAppIconLargeConstants.insets + } + + var iconView: DAppIconView { titleView.fView } + + var titleLabel: UILabel { titleView.sView } + + let tableView = StackTableView() + let walletCell = StackTableCell() + let dappCell = StackTableCell() + let networksCell = StackInfoTableCell() + let statusCell = StackStatusCell() + + override func setupStyle() { + super.setupStyle() + + stackView.layoutMargins = UIEdgeInsets(top: 32.0, left: 16.0, bottom: 0.0, right: 16.0) + } + + override func setupLayout() { + super.setupLayout() + + addArrangedSubview(titleView, spacingAfter: 24) + + iconView.snp.makeConstraints { make in + make.size.equalTo(DAppIconLargeConstants.size) + } + + addArrangedSubview(tableView) + + tableView.addArrangedSubview(walletCell) + tableView.addArrangedSubview(dappCell) + tableView.addArrangedSubview(networksCell) + tableView.addArrangedSubview(statusCell) + } + + func bind(viewModel: WalletConnectSessionViewModel, locale: Locale) { + iconView.bind(viewModel: viewModel.iconViewModel, size: DAppIconLargeConstants.displaySize) + + titleLabel.text = viewModel.title + walletCell.bind(viewModel: viewModel.wallet?.cellViewModel) + dappCell.bind(details: viewModel.host) + + networksCell.bindNetworks(viewModel: viewModel.networks, locale: locale) + + statusCell.bind(status: viewModel.status, locale: locale) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift new file mode 100644 index 0000000000..4fb23b1b57 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift @@ -0,0 +1,7 @@ +import Foundation + +final class WalletConnectSessionDetailsWireframe: WalletConnectSessionDetailsWireframeProtocol { + func close(view: WalletConnectSessionDetailsViewProtocol?) { + view?.controller.navigationController?.popViewController(animated: true) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift index 40899b43f1..e916e1336c 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsViewFactory.swift @@ -9,7 +9,7 @@ struct WalletConnectSessionsViewFactory { return nil } - let wireframe = WalletConnectSessionsWireframe() + let wireframe = WalletConnectSessionsWireframe(dappMediator: dappMediator) let localizationManager = LocalizationManager.shared diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift index 958fb58bf1..5c9dad4378 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsWireframe.swift @@ -1,11 +1,28 @@ import Foundation final class WalletConnectSessionsWireframe: WalletConnectSessionsWireframeProtocol { - func showSession(from _: WalletConnectSessionsViewProtocol?, details _: WalletConnectSession) { - // TODO: Present session details + let dappMediator: DAppInteractionMediating + + init(dappMediator: DAppInteractionMediating) { + self.dappMediator = dappMediator + } + + func showSession(from view: WalletConnectSessionsViewProtocol?, details: WalletConnectSession) { + guard + let detailsView = WalletConnectSessionDetailsViewFactory.createView( + for: details, + dappMediator: dappMediator + ) else { + return + } + + view?.controller.navigationController?.pushViewController( + detailsView.controller, + animated: true + ) } func close(view: WalletConnectSessionsViewProtocol?) { - view?.controller.navigationController?.popViewController(animated: true) + view?.controller.navigationController?.popToRootViewController(animated: true) } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index 37887f7bc4..c5019f5d66 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -10,6 +10,8 @@ protocol WalletConnectTransportProtocol: DAppTransportProtocol { func getSessionsCount() -> Int func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) } protocol WalletConnectTransportDelegate: AnyObject { @@ -120,12 +122,8 @@ extension WalletConnectTransport: WalletConnectTransportProtocol { let operations = [allSettingsOperation, allWalletsOperation, mapOperation] - mapOperation.completionBlock = { [weak self] in + mapOperation.completionBlock = { DispatchQueue.main.async { - guard let self = self else { - return - } - do { let sessions = try mapOperation.extractNoCancellableResultData() @@ -138,6 +136,10 @@ extension WalletConnectTransport: WalletConnectTransportProtocol { dataSource.operationQueue.addOperations(operations, waitUntilFinished: false) } + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) { + service.disconnect(from: session, completion: completion) + } } extension WalletConnectTransport { @@ -250,8 +252,8 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { delegate?.walletConnect(transport: self, didReceive: .proposal(proposal)) } - func walletConnect(service _: WalletConnectServiceProtocol, establishedSession: Session) { - logger.debug("New session: \(establishedSession)") + func walletConnect(service _: WalletConnectServiceProtocol, didChange sessions: [Session]) { + logger.debug("Sessions number: \(sessions.count)") delegate?.walletConnectDidChangeSessions(transport: self) } diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift new file mode 100644 index 0000000000..0a5b884696 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift @@ -0,0 +1,10 @@ +import Foundation + +extension StackInfoTableCell { + func bindNetworks(viewModel: WalletConnectNetworksViewModel, locale: Locale) { + titleLabel.text = R.string.localizable.commonNetworksTitle( + viewModel.totalNetworks, + preferredLanguages: locale.rLanguages + ) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackStatusCell+WalletConnect.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackStatusCell+WalletConnect.swift new file mode 100644 index 0000000000..8f7f9844c0 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackStatusCell+WalletConnect.swift @@ -0,0 +1,18 @@ +import Foundation + +extension StackStatusCell { + func bind(status: WalletConnectSessionViewModel.Status, locale: Locale) { + switch status { + case .active: + statusView.apply(style: .active) + statusView.titleLabel.text = R.string.localizable.commonStatusActive( + preferredLanguages: locale.rLanguages + ) + case .expired: + statusView.apply(style: .inactive) + statusView.titleLabel.text = R.string.localizable.commonStatusExpired( + preferredLanguages: locale.rLanguages + ) + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift new file mode 100644 index 0000000000..01bee5296b --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift @@ -0,0 +1,11 @@ +import Foundation + +struct WalletConnectNetworksViewModel { + let network: NetworkViewModel? + let supported: Int + let unsupported: Int + + var totalNetworks: Int { + supported + unsupported + } +} diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index eb2808bc19..fc236c9bfb 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1212,3 +1212,7 @@ "wallet.connect.scan.message" = "Scan QR from DApp"; "wallet.connect.scan.error" = "Invalid URI format"; "wallet.connect.scan.button" = "New connection"; +"common.status.active" = "Active"; +"common.status.expired" = "Expired"; +"common.disconnect" = "Disconnect"; +"common.status" = "Status"; diff --git a/novawallet/en.lproj/Localizable.stringsdict b/novawallet/en.lproj/Localizable.stringsdict index 2d79e911da..c0555408c1 100644 --- a/novawallet/en.lproj/Localizable.stringsdict +++ b/novawallet/en.lproj/Localizable.stringsdict @@ -1,118 +1,134 @@ - - common.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li day - other - %li days - - - common.hours.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li hour - other - %li hours - - - common.days.left.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li day left - other - %li days left - - - staking.analytics.validators.eras.counter - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li era - other - %li eras - - - common.minutes.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li minute - other - %li minutes - - - common.every.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - everyday - other - every %li days - - - common.in.tracks - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 track - other - %li tracks - - - - \ No newline at end of file + + common.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li day + other + %li days + + + common.hours.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li hour + other + %li hours + + + common.days.left.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li day left + other + %li days left + + + staking.analytics.validators.eras.counter + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li era + other + %li eras + + + common.minutes.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li minute + other + %li minutes + + + common.every.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + everyday + other + every %li days + + + common.in.tracks + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 track + other + %li tracks + + + common.networks.title + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Network + other + Networks + + + + diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 3f7ade954e..0af2db1d65 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1211,3 +1211,7 @@ "wallet.connect.scan.message" = "Отсканируйте QR код"; "wallet.connect.scan.error" = "Неверный формат URI"; "wallet.connect.scan.button" = "Новое соединение"; +"common.status.active" = "Активный"; +"common.status.expired" = "Истёк"; +"common.disconnect" = "Отключить"; +"common.status" = "Статус"; diff --git a/novawallet/ru.lproj/Localizable.stringsdict b/novawallet/ru.lproj/Localizable.stringsdict index 577322d7ba..99ab968dc2 100644 --- a/novawallet/ru.lproj/Localizable.stringsdict +++ b/novawallet/ru.lproj/Localizable.stringsdict @@ -1,146 +1,162 @@ - - common.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li день - few - %li дня - many - %li дней - other - %li дней - - - common.hours.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li час - few - %li часа - many - %li часов - other - %li час - - - common.days.left.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - Остался %li день - few - Осталось %li дня - many - Осталось %li дней - other - Осталось %li дней - - - staking.analytics.validators.eras.counter - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li эра - few - %li эр - many - %li эр - other - %li эры - - - common.minutes.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li минута - few - %li минуты - many - %li минут - other - %li минута - - - common.every.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - каждый день - few - каждые %li дня - many - каждые %li дней - other - каждые %li дней - - - common.in.tracks - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 треке - few - %li треках - many - %li треках - other - %li треках - - - - \ No newline at end of file + + common.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li день + few + %li дня + many + %li дней + other + %li дней + + + common.hours.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li час + few + %li часа + many + %li часов + other + %li час + + + common.days.left.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Остался %li день + few + Осталось %li дня + many + Осталось %li дней + other + Осталось %li дней + + + staking.analytics.validators.eras.counter + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li эра + few + %li эр + many + %li эр + other + %li эры + + + common.minutes.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li минута + few + %li минуты + many + %li минут + other + %li минута + + + common.every.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + каждый день + few + каждые %li дня + many + каждые %li дней + other + каждые %li дней + + + common.in.tracks + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 треке + few + %li треках + many + %li треках + other + %li треках + + + common.networks.title + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Сеть + other + Сети + + + + From deb88d628a9686399fae1608c23083ec0e9fa7df Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 5 May 2023 16:35:45 +0500 Subject: [PATCH 26/54] session details ui --- novawallet.xcodeproj/project.pbxproj | 4 +++ .../View/StackTable/StackInfoTableCell.swift | 24 ++++++++++++++ .../Common/ViewModel/NetworkViewModel.swift | 6 ++++ .../Model/WalletConnectModelFactory.swift | 10 ++++++ .../Model/WalletConnectSession.swift | 1 + .../Service/WalletConnectInteractor.swift | 10 ++++++ .../Service/WalletConnectProtocols.swift | 8 +++++ ...WalletConnectSessionViewModelFactory.swift | 5 ++- ...alletConnectSessionDetailsInteractor.swift | 4 +++ ...WalletConnectSessionDetailsPresenter.swift | 2 ++ ...tConnectSessionDetailsViewController.swift | 1 + ...alletConnectSessionDetailsViewLayout.swift | 2 ++ .../WalletConnectSessionsInteractor.swift | 4 +++ .../Transport/WalletConnectTransport.swift | 18 ++++++++-- .../StackInfoTableCell+WallectConnect.swift | 33 +++++++++++++++++++ ...alletConnectNetworksViewModelFactory.swift | 17 ++++++++++ novawallet/en.lproj/Localizable.strings | 1 + novawallet/en.lproj/Localizable.stringsdict | 16 +++++++++ novawallet/ru.lproj/Localizable.strings | 1 + novawallet/ru.lproj/Localizable.stringsdict | 16 +++++++++ 20 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 8b1d51bd41..e236bb0d09 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -487,6 +487,7 @@ 840B3D70289A575A00DA1DA9 /* ParitySignerScanProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840B3D6F289A575A00DA1DA9 /* ParitySignerScanProtocols.swift */; }; 840BF22526C2653100E3A955 /* ChainSyncServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BF22426C2653100E3A955 /* ChainSyncServiceTests.swift */; }; 840BF22726C2C8A600E3A955 /* ChainSyncEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BF22626C2C8A600E3A955 /* ChainSyncEvents.swift */; }; + 840D55972A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */; }; 840D626F29CB39EE00D5E894 /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D626E29CB39EE00D5E894 /* URLBuilder.swift */; }; 840D627129CB3FD900D5E894 /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D627029CB3FD900D5E894 /* URLBuilderTests.swift */; }; 840D627529CB46E900D5E894 /* ConnectionApiKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D627429CB46E900D5E894 /* ConnectionApiKeys.swift */; }; @@ -3940,6 +3941,7 @@ 840B3D6F289A575A00DA1DA9 /* ParitySignerScanProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParitySignerScanProtocols.swift; sourceTree = ""; }; 840BF22426C2653100E3A955 /* ChainSyncServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainSyncServiceTests.swift; sourceTree = ""; }; 840BF22626C2C8A600E3A955 /* ChainSyncEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainSyncEvents.swift; sourceTree = ""; }; + 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectNetworksViewModelFactory.swift; sourceTree = ""; }; 840D626D29CB32F100D5E894 /* SubstrateDataModel12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SubstrateDataModel12.xcdatamodel; sourceTree = ""; }; 840D626E29CB39EE00D5E894 /* URLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilder.swift; sourceTree = ""; }; 840D627029CB3FD900D5E894 /* URLBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilderTests.swift; sourceTree = ""; }; @@ -13518,6 +13520,7 @@ 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */, 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */, 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */, + 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */, ); path = ViewModel; sourceTree = ""; @@ -18509,6 +18512,7 @@ 84C2F27725E296CD0050A4AD /* RewardDestinationViewModelFactory.swift in Sources */, 84D97EC82520D32000F07405 /* PolkadotIcon+Image.swift in Sources */, 84C2802126F541DE006E8014 /* WebSocketEngine+Connection.swift in Sources */, + 840D55972A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift in Sources */, 84CA68D326BE9A35003B9453 /* RuntimeProvider.swift in Sources */, 8401620625E130D60087A5F3 /* ViewAction.swift in Sources */, 84A2C90424E07F400020D3B7 /* AccountOperationFactoryError.swift in Sources */, diff --git a/novawallet/Common/View/StackTable/StackInfoTableCell.swift b/novawallet/Common/View/StackTable/StackInfoTableCell.swift index e57ad271ca..71687d562e 100644 --- a/novawallet/Common/View/StackTable/StackInfoTableCell.swift +++ b/novawallet/Common/View/StackTable/StackInfoTableCell.swift @@ -7,6 +7,14 @@ class StackInfoTableCell: RowView WalletConnectChainsResolution { + session.namespaces.values.reduce(WalletConnectChainsResolution()) { result, namespace in + let resolution = resolveChains(from: namespace.chains ?? [], chainsStore: chainsStore) + return result.merging(with: resolution) + } + } + static func resolveChain(for blockchain: Blockchain, chainsStore: ChainsStoreProtocol) -> ChainModel? { let resolution = resolveChains(from: [blockchain], chainsStore: chainsStore) return resolution.resolved.first?.value diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift index 72c01a6c75..4ef25c6889 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSession.swift @@ -4,6 +4,7 @@ struct WalletConnectSession { let sessionId: String let pairingId: String let wallet: MetaAccountModel? + let networks: WalletConnectChainsResolution let dAppName: String? let dAppHost: String? let dAppIcon: URL? diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift index e39156830e..4482d5e775 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -87,6 +87,16 @@ extension WalletConnectInteractor: WalletConnectTransportDelegate { } } + func walletConnectDidChangeChains(transport _: WalletConnectTransportProtocol) { + delegates.forEach { wrapper in + guard let target = wrapper.target as? WalletConnectDelegateOutputProtocol else { + return + } + + return target.walletConnectDidChangeChains() + } + } + func walletConnectAskNextMessage(transport _: WalletConnectTransportProtocol) { mediator?.processMessageQueue() } diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift index e1061648fa..724a226111 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift @@ -17,4 +17,12 @@ protocol WalletConnectDelegateInputProtocol: AnyObject { protocol WalletConnectDelegateOutputProtocol: AnyObject { func walletConnectDidChangeSessions() + + func walletConnectDidChangeChains() +} + +extension WalletConnectDelegateOutputProtocol { + func walletConnectDidChangeSessions() {} + + func walletConnectDidChangeChains() {} } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift index fc741d185c..f9f23e4b72 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift @@ -6,6 +6,7 @@ protocol WalletConnectSessionViewModelFactoryProtocol { final class WalletConnectSessionViewModelFactory: WalletConnectSessionViewModelFactoryProtocol { let walletViewModelFactory = WalletAccountViewModelFactory() + let networksViewModelFactory = WalletConnectNetworksViewModelFactory() func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionViewModel { let walletViewModel: DisplayWalletViewModel? = model.wallet.flatMap { wallet in @@ -21,12 +22,14 @@ final class WalletConnectSessionViewModelFactory: WalletConnectSessionViewModelF iconViewModel = StaticImageViewModel(image: icon) } + let networks = networksViewModelFactory.createViewModel(from: model.networks) + return .init( iconViewModel: iconViewModel, title: model.dAppName ?? "", wallet: walletViewModel, host: model.dAppHost ?? "", - networks: .init(network: nil, supported: 0, unsupported: 0), + networks: networks, status: model.active ? .active : .expired ) } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift index f4c5d80425..7b057d311d 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractor.swift @@ -49,4 +49,8 @@ extension WalletConnectSessionDetailsInteractor: WalletConnectDelegateOutputProt func walletConnectDidChangeSessions() { updateSession(for: sessionId) } + + func walletConnectDidChangeChains() { + updateSession(for: sessionId) + } } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift index 3fcc526401..53da2cebc1 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift @@ -42,6 +42,8 @@ final class WalletConnectSessionDetailsPresenter { extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsPresenterProtocol { func setup() { + updateView() + interactor.setup() } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift index ce0b0d4268..e4b9316af7 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewController.swift @@ -28,6 +28,7 @@ final class WalletConnectSessionViewController: UIViewController, ViewHolder { override func viewDidLoad() { super.viewDidLoad() + setupHandlers() setupLocalization() presenter.setup() diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift index e36c9de5b2..451bb5bb7f 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift @@ -23,6 +23,8 @@ final class WalletConnectSessionDetailsViewLayout: ScrollableContainerActionLayo super.setupStyle() stackView.layoutMargins = UIEdgeInsets(top: 32.0, left: 16.0, bottom: 0.0, right: 16.0) + + actionLoadableView.actionButton.applyDestructiveDefaultStyle() } override func setupLayout() { diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift index 3e61bc0b1e..79be86906d 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift @@ -41,4 +41,8 @@ extension WalletConnectSessionsInteractor: WalletConnectDelegateOutputProtocol { func walletConnectDidChangeSessions() { provideSessions() } + + func walletConnectDidChangeChains() { + provideSessions() + } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index c5019f5d66..f5271a8fc2 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -32,6 +32,8 @@ protocol WalletConnectTransportDelegate: AnyObject { func walletConnectDidChangeSessions(transport: WalletConnectTransportProtocol) + func walletConnectDidChangeChains(transport: WalletConnectTransportProtocol) + func walletConnectAskNextMessage(transport: WalletConnectTransportProtocol) } @@ -57,7 +59,8 @@ final class WalletConnectTransport { private func createSessionsMappingOperation( dependingOn allSettingsOperation: BaseOperation<[DAppSettings]>, allWalletsOperation: BaseOperation<[MetaAccountModel]>, - wcSessions: [Session] + wcSessions: [Session], + chainsStore: ChainsStoreProtocol ) -> BaseOperation<[WalletConnectSession]> { ClosureOperation<[WalletConnectSession]> { let allSettings = try allSettingsOperation.extractNoCancellableResultData().reduceToDict() @@ -65,7 +68,7 @@ final class WalletConnectTransport { return wcSessions.map { wcSession in let dAppIcon = wcSession.peer.icons.first.flatMap { URL(string: $0) } - let active = wcSession.expiryDate.compare(Date()) != .orderedDescending + let active = wcSession.expiryDate.compare(Date()) != .orderedAscending let wallet: MetaAccountModel? @@ -77,10 +80,16 @@ final class WalletConnectTransport { wallet = nil } + let networks = WalletConnectModelFactory.createSessionChainsResolution( + from: wcSession, + chainsStore: chainsStore + ) + return WalletConnectSession( sessionId: wcSession.topic, pairingId: wcSession.pairingTopic, wallet: wallet, + networks: networks, dAppName: wcSession.peer.name, dAppHost: wcSession.peer.url, dAppIcon: dAppIcon, @@ -114,7 +123,8 @@ extension WalletConnectTransport: WalletConnectTransportProtocol { let mapOperation = createSessionsMappingOperation( dependingOn: allSettingsOperation, allWalletsOperation: allWalletsOperation, - wcSessions: wcSessions + wcSessions: wcSessions, + chainsStore: dataSource.chainsStore ) mapOperation.addDependency(allSettingsOperation) @@ -172,6 +182,8 @@ extension WalletConnectTransport { func processChainsChanges() { state?.proceed(with: dataSource) + + delegate?.walletConnectDidChangeChains(transport: self) } func start() { diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift index 0a5b884696..905354d30e 100644 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift @@ -6,5 +6,38 @@ extension StackInfoTableCell { viewModel.totalNetworks, preferredLanguages: locale.rLanguages ) + + if let networkViewModel = viewModel.network { + if viewModel.totalNetworks > 1 { + canSelect = true + + let details = R.string.localizable.commonMoreFormat( + networkViewModel.name, + "\(viewModel.totalNetworks - 1)", + preferredLanguages: locale.rLanguages + ) + + bind(details: details) + } else { + canSelect = false + + bind(viewModel: networkViewModel.cellViewModel) + } + } else if viewModel.unsupported > 0 { + canSelect = true + + let details = R.string.localizable.commonUnsupportedCount( + format: viewModel.unsupported, + preferredLanguages: locale.rLanguages + ) + + bind(details: details) + } else { + canSelect = false + + let details = R.string.localizable.commonNone(preferredLanguages: locale.rLanguages) + + bind(details: details) + } } } diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift new file mode 100644 index 0000000000..bc816fabf9 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift @@ -0,0 +1,17 @@ +import Foundation + +final class WalletConnectNetworksViewModelFactory { + private lazy var networkViewModelFactory = NetworkViewModelFactory() + + func createViewModel(from networks: WalletConnectChainsResolution) -> WalletConnectNetworksViewModel { + let optNetwork = networks.resolved.values.min { $0.addressPrefix < $1.addressPrefix } + + let networkViewModel = optNetwork.map { networkViewModelFactory.createViewModel(from: $0) } + + return .init( + network: networkViewModel, + supported: networks.resolved.count, + unsupported: networks.unresolved.count + ) + } +} diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index fc236c9bfb..d8eaebe305 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1216,3 +1216,4 @@ "common.status.expired" = "Expired"; "common.disconnect" = "Disconnect"; "common.status" = "Status"; +"common.none" = "None"; diff --git a/novawallet/en.lproj/Localizable.stringsdict b/novawallet/en.lproj/Localizable.stringsdict index c0555408c1..b41ba2928a 100644 --- a/novawallet/en.lproj/Localizable.stringsdict +++ b/novawallet/en.lproj/Localizable.stringsdict @@ -130,5 +130,21 @@ Networks + common.unsupported.count + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 unsupported + other + %li unsupported + + diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 0af2db1d65..5cc00766cf 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1215,3 +1215,4 @@ "common.status.expired" = "Истёк"; "common.disconnect" = "Отключить"; "common.status" = "Статус"; +"common.none" = "Отсутствуют"; diff --git a/novawallet/ru.lproj/Localizable.stringsdict b/novawallet/ru.lproj/Localizable.stringsdict index 99ab968dc2..4bcfa17eef 100644 --- a/novawallet/ru.lproj/Localizable.stringsdict +++ b/novawallet/ru.lproj/Localizable.stringsdict @@ -158,5 +158,21 @@ Сети + common.unsupported.count + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 не поддерживается + other + %li не поддерживаются + + From b59050a2e1f958df8d4bd3ea0d7e0b30773a3687 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 5 May 2023 16:44:26 +0500 Subject: [PATCH 27/54] sessions ordering --- .../DApp/WalletConnect/Transport/WalletConnectTransport.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index f5271a8fc2..ae62b3d11b 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -66,7 +66,9 @@ final class WalletConnectTransport { let allSettings = try allSettingsOperation.extractNoCancellableResultData().reduceToDict() let allWallets = try allWalletsOperation.extractNoCancellableResultData().reduceToDict() - return wcSessions.map { wcSession in + return wcSessions.sorted( + by: { $0.expiryDate.compare($1.expiryDate) == .orderedDescending } + ).map { wcSession in let dAppIcon = wcSession.peer.icons.first.flatMap { URL(string: $0) } let active = wcSession.expiryDate.compare(Date()) != .orderedAscending From f7a8af75a7090faa948808a21acbe2833cac27d9 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 5 May 2023 18:32:19 +0500 Subject: [PATCH 28/54] fix session details ui --- novawallet.xcodeproj/project.pbxproj | 8 ++ .../View/StackTable/StackInfoTableCell.swift | 41 ++++-- .../Cell/NetworkTableViewCell.swift | 36 ++++++ .../ModalPicker/ModalNetworksFactory.swift | 121 ++++++++++++++++++ .../ModalPicker/ModalPickerFactory.swift | 71 ---------- .../DAppOperationConfirmPresenter.swift | 2 +- ...WalletConnectSessionDetailsPresenter.swift | 8 +- ...WalletConnectSessionDetailsProtocols.swift | 1 + ...WalletConnectSessionDetailsWireframe.swift | 12 ++ .../StackInfoTableCell+WallectConnect.swift | 2 +- ...alletConnectNetworksViewModelFactory.swift | 4 +- .../TransferSetupWireframe.swift | 2 +- 12 files changed, 223 insertions(+), 85 deletions(-) create mode 100644 novawallet/Common/ViewController/ModalPicker/Cell/NetworkTableViewCell.swift create mode 100644 novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index e236bb0d09..5a3a390e60 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1507,6 +1507,8 @@ 8488ECD7258CDCBC004591CC /* PurchaseCompletionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8488ECD6258CDCBC004591CC /* PurchaseCompletionHandler.swift */; }; 8488ECDF258CE118004591CC /* PurchaseCompleted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8488ECDE258CE118004591CC /* PurchaseCompleted.swift */; }; 8488ECEA258CE456004591CC /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8488ECE9258CE456004591CC /* PurchaseViewController.swift */; }; + 8489198E2A0529DA008D57A3 /* NetworkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8489198D2A0529DA008D57A3 /* NetworkTableViewCell.swift */; }; + 848919902A052C18008D57A3 /* ModalNetworksFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8489198F2A052C18008D57A3 /* ModalNetworksFactory.swift */; }; 848919D726FB238E004DBAD5 /* JsonDataProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848919D626FB238E004DBAD5 /* JsonDataProviderFactory.swift */; }; 848919DB26FB663D004DBAD5 /* CrowdloansChainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848919DA26FB663D004DBAD5 /* CrowdloansChainViewModel.swift */; }; 84893BFC24D9B0F1008F6A3F /* PredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84893BFB24D9B0F1008F6A3F /* PredicateTests.swift */; }; @@ -4973,6 +4975,8 @@ 8488ECD6258CDCBC004591CC /* PurchaseCompletionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseCompletionHandler.swift; sourceTree = ""; }; 8488ECDE258CE118004591CC /* PurchaseCompleted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseCompleted.swift; sourceTree = ""; }; 8488ECE9258CE456004591CC /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = ""; }; + 8489198D2A0529DA008D57A3 /* NetworkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkTableViewCell.swift; sourceTree = ""; }; + 8489198F2A052C18008D57A3 /* ModalNetworksFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalNetworksFactory.swift; sourceTree = ""; }; 848919D626FB238E004DBAD5 /* JsonDataProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonDataProviderFactory.swift; sourceTree = ""; }; 848919DA26FB663D004DBAD5 /* CrowdloansChainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloansChainViewModel.swift; sourceTree = ""; }; 84893BFB24D9B0F1008F6A3F /* PredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredicateTests.swift; sourceTree = ""; }; @@ -13653,6 +13657,7 @@ 84D8F16C24D82C7E00AF43E9 /* ModalPickerConfiguration.swift */, 84DF21A4253473B0005454AE /* ModalInfoFactory.swift */, 844DBC63274D2BD6009F8351 /* ModalPickerClosureContext.swift */, + 8489198F2A052C18008D57A3 /* ModalNetworksFactory.swift */, ); path = ModalPicker; sourceTree = ""; @@ -13675,6 +13680,7 @@ 8471825E2846A8E2002C5720 /* ActionManageTableViewCell.swift */, 8436B6D728480D2F00F24360 /* ModalPickerActionTableViewCell.swift */, 842B17FC2864980B0014CC57 /* NetworkSelectionTableViewCell.swift */, + 8489198D2A0529DA008D57A3 /* NetworkTableViewCell.swift */, ); path = Cell; sourceTree = ""; @@ -17318,6 +17324,7 @@ AE2C84D525EF989A00986716 /* ValidatorInfoWireframe.swift in Sources */, 849D14CC2994EEA50048E947 /* GovernanceDelegateFlowDisplayInfo.swift in Sources */, F44CD8F426242825005DDF23 /* PayoutRewardsService+Fetch.swift in Sources */, + 8489198E2A0529DA008D57A3 /* NetworkTableViewCell.swift in Sources */, 84E4897329A79017008726FA /* SkeletonDecoration+View.swift in Sources */, 2AD0A19525D3D3EC00312428 /* GitHubOperationFactory.swift in Sources */, 845B07FF2916F529005785D3 /* Gov1SubscriptionFactory.swift in Sources */, @@ -18096,6 +18103,7 @@ 84F1A073286A5DDA007DB053 /* CommonRetryable.swift in Sources */, 845B821526EF657700D25C72 /* PersistentValueSettings.swift in Sources */, F4DCAE4726207EF900CCA6BF /* PayoutRewardsServiceProtocol.swift in Sources */, + 848919902A052C18008D57A3 /* ModalNetworksFactory.swift in Sources */, 8463A6F925E2F82E003B8160 /* CDSingleValue+CoreDataCodable.swift in Sources */, 84ED6BE6286995F400B3C558 /* TransferCrossChainConfirmPresenter.swift in Sources */, 84EBFCE7285E7A7C0006327E /* XcmMessage.swift in Sources */, diff --git a/novawallet/Common/View/StackTable/StackInfoTableCell.swift b/novawallet/Common/View/StackTable/StackInfoTableCell.swift index 71687d562e..5f13b3f8f4 100644 --- a/novawallet/Common/View/StackTable/StackInfoTableCell.swift +++ b/novawallet/Common/View/StackTable/StackInfoTableCell.swift @@ -28,14 +28,26 @@ class StackInfoTableCell: RowView( + for title: LocalizableResource + ) -> ModalPickerViewController { + let viewController: ModalPickerViewController + = ModalPickerViewController(nib: R.nib.modalPickerViewController) + + viewController.localizedTitle = title + + viewController.modalPresentationStyle = .custom + viewController.separatorStyle = .none + viewController.cellHeight = 52.0 + viewController.headerHeight = 40.0 + viewController.footerHeight = 0.0 + viewController.headerBorderType = [] + + viewController.actionType = .none + + return viewController + } + + static func createNetworkSelectionList( + selectionState: CrossChainDestinationSelectionState, + delegate: ModalPickerViewControllerDelegate?, + context: AnyObject? + ) -> UIViewController? { + let viewController: ModalPickerViewController + + let title = LocalizableResource { locale in + R.string.localizable.xcmDestinationSelectionTitle(preferredLanguages: locale.rLanguages) + } + + viewController = createNetworksController(for: title) + viewController.delegate = delegate + viewController.context = context + + let networkViewModelFactory = NetworkViewModelFactory() + + let onChainViewModel = LocalizableResource { _ in + networkViewModelFactory.createViewModel(from: selectionState.originChain) + } + + let onChainTitle = LocalizableResource { locale in + R.string.localizable.commonOnChain(preferredLanguages: locale.rLanguages) + } + + viewController.addSection(viewModels: [onChainViewModel], title: onChainTitle) + + let crossChainViewModels = selectionState.availableDestChains.map { chain in + LocalizableResource { _ in networkViewModelFactory.createViewModel(from: chain) } + } + + let crossChainTitle = LocalizableResource { locale in + R.string.localizable.commonCrossChain(preferredLanguages: locale.rLanguages) + } + + viewController.addSection(viewModels: crossChainViewModels, title: crossChainTitle) + + if selectionState.selectedChainId == selectionState.originChain.chainId { + viewController.selectedIndex = 0 + viewController.selectedSection = 0 + } else if let index = selectionState.availableDestChains.firstIndex( + where: { selectionState.selectedChainId == $0.chainId } + ) { + viewController.selectedIndex = index + viewController.selectedSection = 1 + } else { + viewController.selectedIndex = NSNotFound + } + + let factory = ModalSheetPresentationFactory(configuration: .nova) + viewController.modalTransitioningFactory = factory + + let itemsCount = crossChainViewModels.count + 1 + let sectionsCount = 2 + let height = viewController.headerHeight + CGFloat(itemsCount) * viewController.cellHeight + + CGFloat(sectionsCount) * viewController.sectionHeaderHeight + viewController.footerHeight + viewController.preferredContentSize = CGSize(width: 0.0, height: height) + + viewController.localizationManager = LocalizationManager.shared + + return viewController + } + + static func createNetworksInfoList(for networks: [ChainModel]) -> UIViewController? { + let viewController: ModalPickerViewController + + let title = LocalizableResource { locale in + R.string.localizable.commonNetworksTitle( + networks.count, + preferredLanguages: locale.rLanguages + ) + } + + viewController = createNetworksController(for: title) + + let networkViewModelFactory = NetworkViewModelFactory() + + let viewModels = networks.map { network in + LocalizableResource { _ in + networkViewModelFactory.createViewModel(from: network) + } + } + + viewController.viewModels = viewModels + + let factory = ModalSheetPresentationFactory(configuration: .nova) + viewController.modalTransitioningFactory = factory + + let height = viewController.headerHeight + CGFloat(networks.count) * viewController.cellHeight + viewController.preferredContentSize = CGSize(width: 0.0, height: height) + + viewController.localizationManager = LocalizationManager.shared + + return viewController + } +} diff --git a/novawallet/Common/ViewController/ModalPicker/ModalPickerFactory.swift b/novawallet/Common/ViewController/ModalPicker/ModalPickerFactory.swift index a2527697c7..f63b42f454 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalPickerFactory.swift +++ b/novawallet/Common/ViewController/ModalPicker/ModalPickerFactory.swift @@ -436,77 +436,6 @@ enum ModalPickerFactory { return viewController } - - static func createNetworkSelectionList( - selectionState: CrossChainDestinationSelectionState, - delegate: ModalPickerViewControllerDelegate?, - context: AnyObject? - ) -> UIViewController? { - let viewController: ModalPickerViewController - = ModalPickerViewController(nib: R.nib.modalPickerViewController) - - viewController.localizedTitle = LocalizableResource { locale in - R.string.localizable.xcmDestinationSelectionTitle(preferredLanguages: locale.rLanguages) - } - - viewController.delegate = delegate - viewController.modalPresentationStyle = .custom - viewController.context = context - viewController.separatorStyle = .none - viewController.cellHeight = 52.0 - viewController.headerHeight = 40.0 - viewController.footerHeight = 0.0 - viewController.headerBorderType = [] - - viewController.actionType = .none - - let networkViewModelFactory = NetworkViewModelFactory() - - let onChainViewModel = LocalizableResource { _ in - networkViewModelFactory.createViewModel(from: selectionState.originChain) - } - - let onChainTitle = LocalizableResource { locale in - R.string.localizable.commonOnChain(preferredLanguages: locale.rLanguages) - } - - viewController.addSection(viewModels: [onChainViewModel], title: onChainTitle) - - let crossChainViewModels = selectionState.availableDestChains.map { chain in - LocalizableResource { _ in networkViewModelFactory.createViewModel(from: chain) } - } - - let crossChainTitle = LocalizableResource { locale in - R.string.localizable.commonCrossChain(preferredLanguages: locale.rLanguages) - } - - viewController.addSection(viewModels: crossChainViewModels, title: crossChainTitle) - - if selectionState.selectedChainId == selectionState.originChain.chainId { - viewController.selectedIndex = 0 - viewController.selectedSection = 0 - } else if let index = selectionState.availableDestChains.firstIndex( - where: { selectionState.selectedChainId == $0.chainId } - ) { - viewController.selectedIndex = index - viewController.selectedSection = 1 - } else { - viewController.selectedIndex = NSNotFound - } - - let factory = ModalSheetPresentationFactory(configuration: .nova) - viewController.modalTransitioningFactory = factory - - let itemsCount = crossChainViewModels.count + 1 - let sectionsCount = 2 - let height = viewController.headerHeight + CGFloat(itemsCount) * viewController.cellHeight + - CGFloat(sectionsCount) * viewController.sectionHeaderHeight + viewController.footerHeight - viewController.preferredContentSize = CGSize(width: 0.0, height: height) - - viewController.localizationManager = LocalizationManager.shared - - return viewController - } } extension ModalPickerFactory { diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift index f48fc57a34..6033c9ad1a 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift @@ -47,7 +47,7 @@ final class DAppOperationConfirmPresenter { } private func provideFeeViewModel() { - guard let feeModel = feeModel, let confirmationModel = confirmationModel else { + guard let feeModel = feeModel else { view?.didReceive(feeViewModel: .loading) return } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift index 53da2cebc1..a1b02eb15e 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift @@ -47,7 +47,13 @@ extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsPrese interactor.setup() } - func presentNetworks() {} + func presentNetworks() { + let networks = session.networks.resolved.values.sorted { + ChainModelCompator.defaultComparator(chain1: $0, chain2: $1) + } + + wireframe.showNetworks(from: view, networks: networks) + } func disconnect() { performDisconnect() diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift index 20e1640d62..709d15ba25 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift @@ -23,4 +23,5 @@ protocol WalletConnectSessionDetailsInteractorOutputProtocol: AnyObject { protocol WalletConnectSessionDetailsWireframeProtocol: AlertPresentable, ErrorPresentable, CommonRetryable { func close(view: WalletConnectSessionDetailsViewProtocol?) + func showNetworks(from view: WalletConnectSessionDetailsViewProtocol?, networks: [ChainModel]) } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift index 4fb23b1b57..4f562d4dd0 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsWireframe.swift @@ -2,6 +2,18 @@ import Foundation final class WalletConnectSessionDetailsWireframe: WalletConnectSessionDetailsWireframeProtocol { func close(view: WalletConnectSessionDetailsViewProtocol?) { + if let presentingViewController = view?.controller.presentedViewController { + presentingViewController.dismiss(animated: false) + } + view?.controller.navigationController?.popViewController(animated: true) } + + func showNetworks(from view: WalletConnectSessionDetailsViewProtocol?, networks: [ChainModel]) { + guard let viewController = ModalNetworksFactory.createNetworksInfoList(for: networks) else { + return + } + + view?.controller.present(viewController, animated: true, completion: nil) + } } diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift index 905354d30e..29e6c9fd03 100644 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift @@ -21,7 +21,7 @@ extension StackInfoTableCell { } else { canSelect = false - bind(viewModel: networkViewModel.cellViewModel) + bind(viewModel: networkViewModel.cellViewModel, cornerRadius: nil) } } else if viewModel.unsupported > 0 { canSelect = true diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift index bc816fabf9..84fe7d6b48 100644 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift @@ -4,7 +4,9 @@ final class WalletConnectNetworksViewModelFactory { private lazy var networkViewModelFactory = NetworkViewModelFactory() func createViewModel(from networks: WalletConnectChainsResolution) -> WalletConnectNetworksViewModel { - let optNetwork = networks.resolved.values.min { $0.addressPrefix < $1.addressPrefix } + let optNetwork = networks.resolved.values.min { + ChainModelCompator.defaultComparator(chain1: $0, chain2: $1) + } let networkViewModel = optNetwork.map { networkViewModelFactory.createViewModel(from: $0) } diff --git a/novawallet/Modules/Transfer/TransferSetup/TransferSetupWireframe.swift b/novawallet/Modules/Transfer/TransferSetup/TransferSetupWireframe.swift index 03dbf6e9e1..dd96082f46 100644 --- a/novawallet/Modules/Transfer/TransferSetup/TransferSetupWireframe.swift +++ b/novawallet/Modules/Transfer/TransferSetup/TransferSetupWireframe.swift @@ -9,7 +9,7 @@ final class TransferSetupWireframe: TransferSetupWireframeProtocol { delegate: ModalPickerViewControllerDelegate, context: AnyObject? ) { - guard let viewController = ModalPickerFactory.createNetworkSelectionList( + guard let viewController = ModalNetworksFactory.createNetworkSelectionList( selectionState: selectionState, delegate: delegate, context: context From b1b08cc3c9cd596b3904644732a2c7ac4fc8f254 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 6 May 2023 11:33:47 +0500 Subject: [PATCH 29/54] fix wallet connect chip --- novawallet.xcodeproj/project.pbxproj | 12 +++ .../iconConnections.imageset/Contents.json | 12 +++ .../iconConnections.pdf | Bin 0 -> 2997 bytes .../Extension/UIKit/Style/UILabel+Style.swift | 12 ++- .../Common/View/BorderedIconLabelView.swift | 5 + .../TableViewCell/TableViewCellPosition.swift | 14 +++ .../Settings/SettingsViewController.swift | 32 +++--- .../View/SettingsAccessoryTableViewCell.swift | 11 +++ .../View/SettingsBoxTableViewCell.swift | 34 +++++++ .../View/SettingsSubtitleTableViewCell.swift | 15 +++ .../Settings/View/SettingsTableViewCell.swift | 92 +++++++++++------- .../Settings/View/SettingsViewLayout.swift | 2 +- .../ViewModel/SettingsCellViewModel.swift | 27 ++++- .../ViewModel/SettingsViewModelFactory.swift | 22 ++--- .../ReferendumsPersonalActivityView.swift | 2 +- 15 files changed, 227 insertions(+), 65 deletions(-) create mode 100644 novawallet/Assets.xcassets/iconConnections.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconConnections.imageset/iconConnections.pdf create mode 100644 novawallet/Modules/Settings/View/SettingsAccessoryTableViewCell.swift create mode 100644 novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift create mode 100644 novawallet/Modules/Settings/View/SettingsSubtitleTableViewCell.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 5a3a390e60..f132e3b412 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1014,6 +1014,9 @@ 844B2E7C27C426A7000CC079 /* NftLocalSubscriptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B2E7B27C426A7000CC079 /* NftLocalSubscriptionHandler.swift */; }; 844B6EC528A3153C00A8BE83 /* MetaAccountModelType+SignatureFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B6EC428A3153C00A8BE83 /* MetaAccountModelType+SignatureFormat.swift */; }; 844B6EC728A3B05300A8BE83 /* MessageSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B6EC628A3B05300A8BE83 /* MessageSheetViewModel.swift */; }; + 844C3E5A2A06141F00C4305F /* SettingsSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E592A06141F00C4305F /* SettingsSubtitleTableViewCell.swift */; }; + 844C3E5C2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */; }; + 844C3E5E2A06180900C4305F /* SettingsBoxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */; }; 844CB56226F943AD00396E13 /* WalletLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */; }; 844CB56426F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */; }; 844CB56826F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */; }; @@ -4478,6 +4481,9 @@ 844B2E7B27C426A7000CC079 /* NftLocalSubscriptionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftLocalSubscriptionHandler.swift; sourceTree = ""; }; 844B6EC428A3153C00A8BE83 /* MetaAccountModelType+SignatureFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MetaAccountModelType+SignatureFormat.swift"; sourceTree = ""; }; 844B6EC628A3B05300A8BE83 /* MessageSheetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSheetViewModel.swift; sourceTree = ""; }; + 844C3E592A06141F00C4305F /* SettingsSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubtitleTableViewCell.swift; sourceTree = ""; }; + 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccessoryTableViewCell.swift; sourceTree = ""; }; + 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBoxTableViewCell.swift; sourceTree = ""; }; 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanLocalSubscriptionFactory.swift; sourceTree = ""; }; @@ -8383,6 +8389,9 @@ 8428764B24ADDE0200D91AD8 /* SettingsTableViewCell.swift */, F466AA84273D0D4200D14021 /* SettingsSectionHeaderView.swift */, F452D8C9273E58D5008F7295 /* SettingsTableFooterView.swift */, + 844C3E592A06141F00C4305F /* SettingsSubtitleTableViewCell.swift */, + 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */, + 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */, ); path = View; sourceTree = ""; @@ -19316,6 +19325,7 @@ 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, 84B8AA8329F90E3E00347A37 /* WalletConnectStateProtocols.swift in Sources */, + 844C3E5A2A06141F00C4305F /* SettingsSubtitleTableViewCell.swift in Sources */, 88421064289BBD9100306F2C /* Currency.swift in Sources */, 921E4891E85C0DC6FDD8A0D0 /* CrowdloanContributionConfirmInteractor.swift in Sources */, 88A6BCFF28CA15400047E4C2 /* LocksBalanceViewModelFactory.swift in Sources */, @@ -19331,6 +19341,7 @@ 84735E7B2881A57700BADC1B /* AssetsSearchFlowLayout.swift in Sources */, EB9D8D22AA13BF12F845856B /* ReferralCrowdloanProtocols.swift in Sources */, 51FC48FA6FD4D2FB1781424D /* ReferralCrowdloanWireframe.swift in Sources */, + 844C3E5E2A06180900C4305F /* SettingsBoxTableViewCell.swift in Sources */, 0F3E58FC800ED8722589F89E /* ReferralCrowdloanPresenter.swift in Sources */, 84A9ECC1291292900094C763 /* GovernanceUnlockConfirmInteractorError.swift in Sources */, 9F4A48B1BE3A1110A0CF9F36 /* ReferralCrowdloanViewController.swift in Sources */, @@ -19798,6 +19809,7 @@ 211725E26764530359F53A38 /* ParitySignerTxQrInteractor.swift in Sources */, 845B0817291902CF005785D3 /* Gov2LockStateFactory.swift in Sources */, 41DE96F778AE909978775438 /* ParitySignerTxQrViewController.swift in Sources */, + 844C3E5C2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift in Sources */, 87F7556E02F6F5BB6F1B1AEA /* ParitySignerTxQrViewLayout.swift in Sources */, 84770F2C291F893200852A33 /* GovernanceUnlockConfirmInitData.swift in Sources */, 84E90BA128D0B51000529633 /* CheckboxControlView.swift in Sources */, diff --git a/novawallet/Assets.xcassets/iconConnections.imageset/Contents.json b/novawallet/Assets.xcassets/iconConnections.imageset/Contents.json new file mode 100644 index 0000000000..174f5924f5 --- /dev/null +++ b/novawallet/Assets.xcassets/iconConnections.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iconConnections.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconConnections.imageset/iconConnections.pdf b/novawallet/Assets.xcassets/iconConnections.imageset/iconConnections.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2a8c340af752059450bd698172f927401c29e500 GIT binary patch literal 2997 zcmbW3O>f&q5Qgvk6?+Mg9K71^4g7wdy8Lu_dvjPHhT|_c{_EGu=uixg zIzMOS7tiX_fAvcHH7n20Wv5|tD@W_xlQVulZ0-&pln1<)Rr|wkSU+~F*TbLp{c!#E zmAbfD{_6MLAKmKGs=Iw>wM0Q2=Jayb-WK0-92W&0vvsAa{&BW!%i7Dn-|)FOQ*y#_ zD&9G##3Upf1u*Zjk49PTKyp^>YGd>^J%Lgzu}0Nnd0J<+O}6B~y2%-3OCiJ{-ljdz zoJ13>!_;Y;Qw~J|`j}h^%IOqquu-50WUcbJb*Y90)OTJd7G}QygSMe0Ul-m;iU|=P z%Uz?6$%K$3uo^-j5f5F|#pIAU3Q4J%GpnSX$u;3`0r8<${58|QOFA28EJq;$)rMLy zouOEr6fx;x)qEc2X|21!D_ z^M$g^>#UBpl7ode&hC=;V^*eJ`;NMMFX@(A7+ZW^@Q#9#Yf8`vC^YqnB#Yl6u{@5p+r)q|CogRAnA2q(mPfWU~`O zSYx6uV4mPg6y6*Wt1H|Z$u-&0O>8VlO3;Ye#PHC4=-zjm?x#_T?te)!WH|H{Qlzj* z5ZO`0P@*+RajK`2s#g)7e3YUK5^6Hu%crAV&^dTvkdTRZA+(A*=a^uD&>G>8J?Q6C zDd-9w)`G4lj=H2WwaVV48R@$s%Ctt73zJz@D5QxLvU#c$(kCR-_9L70ij;E6fVQ}p zqXI%QJRI8RVu=T6Y+8oMqOTB73ena%_N0{bk>-sm5jetYn_5`vYBpFFD4w&Y^J*8e zK{*d#E}Kj~kK9C^Sr-YVE|KIs4QsaRJCZ@*DS|;c6SJh}gdmoPM6;gk&^xWF9M6z) zwN7YjYmOxPp{v?>LXTjS(U-#M9SG9*d`hHqiL^|#Ra7~+M5}C`XlO3FTqwu1AKju@ zOTU&ppniwcBij(GlbDGCEkqqWOsEpgvDgif23CA9PHHx-qQ=p3z}ZvO1VT5Wh;6?x z>WcQ=$SciDl!Y%EG%ag1@HTR+CGt7x`zMm~|6OuPN17@U|M%OEwon{ekamqsxIWRG z(==R&d%P%o&W@tZP@oRzc{Y`6%DVCXqe;UMId)TXYzZ?g!)z5$ZKB(2z z`fiwjABNle^{eeC^^Q2(4-v z)(_jer>38F!~4#t+#REqzberuq5_^C>0k1w%?Yy5@G0bEl=&QDYWWOGf!xT`e7VdG zdV&tS_5H)J>twM0_BH8vzTZ9$FVyq<+ZV^Atgg4)gOY&91g~!H{~kE~IMl9phcO(- N#nU}|cKPF*e*hjFVO; 1 { + if row == count - 1 { + self = .bottom + } else if row == 0 { + self = .top + } else { + self = .middle + } + } else { + self = .single + } + } } diff --git a/novawallet/Modules/Settings/SettingsViewController.swift b/novawallet/Modules/Settings/SettingsViewController.swift index 90e2013153..12517dcf88 100644 --- a/novawallet/Modules/Settings/SettingsViewController.swift +++ b/novawallet/Modules/Settings/SettingsViewController.swift @@ -38,6 +38,8 @@ final class SettingsViewController: UIViewController, ViewHolder { rootView.tableView.dataSource = self rootView.tableView.delegate = self rootView.tableView.registerClassForCell(SettingsTableViewCell.self) + rootView.tableView.registerClassForCell(SettingsSubtitleTableViewCell.self) + rootView.tableView.registerClassForCell(SettingsBoxTableViewCell.self) rootView.tableView.registerHeaderFooterView(withClass: SettingsSectionHeaderView.self) } @@ -60,23 +62,27 @@ extension SettingsViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithType(SettingsTableViewCell.self)! let viewModels = sections[indexPath.section].1 let cellViewModel = viewModels[indexPath.row] - cell.bind(viewModel: cellViewModel) - - if viewModels.count > 1 { - if indexPath.row == viewModels.count - 1 { - cell.roundView.roundingCorners = [.bottomLeft, .bottomRight] - } else if indexPath.row == 0 { - cell.roundView.roundingCorners = [.topLeft, .topRight] - } else { - cell.roundView.roundingCorners = [] - } - } else { - cell.roundView.roundingCorners = .allCorners + + let cell: SettingsTableViewCell + + switch cellViewModel.accessory { + case let .title(viewModel): + let subtitleCell = tableView.dequeueReusableCellWithType(SettingsSubtitleTableViewCell.self)! + subtitleCell.bind(titleViewModel: cellViewModel.title, accessoryViewModel: viewModel) + cell = subtitleCell + case let .box(viewModel): + let boxCell = tableView.dequeueReusableCellWithType(SettingsBoxTableViewCell.self)! + boxCell.bind(titleViewModel: cellViewModel.title, accessoryViewModel: viewModel) + cell = boxCell + case .none: + cell = tableView.dequeueReusableCellWithType(SettingsTableViewCell.self)! + cell.bind(titleViewModel: cellViewModel.title) } + cell.apply(position: .init(row: indexPath.row, count: viewModels.count)) + return cell } diff --git a/novawallet/Modules/Settings/View/SettingsAccessoryTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsAccessoryTableViewCell.swift new file mode 100644 index 0000000000..02f24d57e6 --- /dev/null +++ b/novawallet/Modules/Settings/View/SettingsAccessoryTableViewCell.swift @@ -0,0 +1,11 @@ +import UIKit + +class SettingsAccessoryTableViewCell: SettingsTableViewCell { + let accessoryDisplayView = A() + + override func setupLayout() { + super.setupLayout() + + contentStackView?.insertArranged(view: accessoryDisplayView, before: accessoryArrowView) + } +} diff --git a/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift new file mode 100644 index 0000000000..0eda804017 --- /dev/null +++ b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift @@ -0,0 +1,34 @@ +import UIKit + +final class SettingsBoxTableViewCell: SettingsAccessoryTableViewCell { + override func setupStyle() { + super.setupStyle() + + accessoryDisplayView.backgroundView.apply(style: .chips) + accessoryDisplayView.backgroundView.cornerRadius = 8 + accessoryDisplayView.iconDetailsView.detailsLabel.numberOfLines = 1 + accessoryDisplayView.iconDetailsView.detailsLabel.apply(style: .footnoteChip) + accessoryDisplayView.iconDetailsView.iconWidth = 12 + accessoryDisplayView.contentInsets = UIEdgeInsets( + top: 2, + left: 8, + bottom: 2, + right: 8 + ) + accessoryDisplayView.iconDetailsView.spacing = 4 + } + + override func setupLayout() { + super.setupLayout() + + accessoryDisplayView.snp.makeConstraints { make in + make.height.equalTo(22) + } + } + + func bind(titleViewModel: TitleIconViewModel, accessoryViewModel: TitleIconViewModel) { + super.bind(titleViewModel: titleViewModel) + + accessoryDisplayView.bind(viewModel: accessoryViewModel) + } +} diff --git a/novawallet/Modules/Settings/View/SettingsSubtitleTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsSubtitleTableViewCell.swift new file mode 100644 index 0000000000..fcf63ece47 --- /dev/null +++ b/novawallet/Modules/Settings/View/SettingsSubtitleTableViewCell.swift @@ -0,0 +1,15 @@ +import UIKit + +final class SettingsSubtitleTableViewCell: SettingsAccessoryTableViewCell { + override func setupStyle() { + super.setupStyle() + + accessoryDisplayView.apply(style: .regularSubhedlineSecondary) + } + + func bind(titleViewModel: TitleIconViewModel, accessoryViewModel: String) { + super.bind(titleViewModel: titleViewModel) + + accessoryDisplayView.text = accessoryViewModel + } +} diff --git a/novawallet/Modules/Settings/View/SettingsTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsTableViewCell.swift index c00225171f..81e2a27ee7 100644 --- a/novawallet/Modules/Settings/View/SettingsTableViewCell.swift +++ b/novawallet/Modules/Settings/View/SettingsTableViewCell.swift @@ -1,38 +1,33 @@ import UIKit import SoraUI -final class SettingsTableViewCell: UITableViewCell { - private let iconImageView = UIImageView() - - private let titleLabel: UILabel = { - let label = UILabel() - label.textColor = R.color.colorTextPrimary() - label.font = .p1Paragraph - return label - }() - - private let subtitleLabel: UILabel = { - let label = UILabel() - label.textColor = R.color.colorTextSecondary() - label.font = .p1Paragraph - return label - }() - - let roundView: RoundedView = { - let view = RoundedView() +class SettingsTableViewCell: UITableViewCell { + let iconImageView = UIImageView() + + let titleLabel: UILabel = .create { $0.apply(style: .regularSubhedlinePrimary) } + + private(set) var contentStackView: UIStackView? + + let roundView: RoundedView = .create { view in view.fillColor = R.color.colorBlockBackground()! view.cornerRadius = 10 view.shadowOpacity = 0.0 - return view - }() + } + + let accessoryArrowView = UIImageView(image: R.image.iconChevronRight()?.tinted( + with: R.color.colorIconSecondary()!) + ) + + let separatorView: BorderedContainerView = .create { view in + view.strokeWidth = 0.5 + view.strokeColor = R.color.colorDivider()! + view.borderType = .bottom + } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - backgroundColor = .clear - - selectionStyle = .none - separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32) + setupStyle() setupLayout() } @@ -47,13 +42,38 @@ final class SettingsTableViewCell: UITableViewCell { roundView.fillColor = highlighted ? R.color.colorCellBackgroundPressed()! : R.color.colorBlockBackground()! } - private func setupLayout() { - let arrowIcon = UIImageView(image: R.image.iconChevronRight()?.tinted(with: R.color.colorIconSecondary()!)) + func setupStyle() { + backgroundColor = .clear + selectionStyle = .none + separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32) + } + + func apply(position: TableViewCellPosition) { + switch position { + case .single: + roundView.roundingCorners = .allCorners + separatorView.borderType = .none + case .top: + roundView.roundingCorners = [.topLeft, .topRight] + separatorView.borderType = .bottom + case .middle: + roundView.roundingCorners = [] + separatorView.borderType = .bottom + case .bottom: + roundView.roundingCorners = [.bottomLeft, .bottomRight] + separatorView.borderType = .none + } + } + + func setupLayout() { let content = UIView.hStack(alignment: .center, spacing: 12, [ - iconImageView, titleLabel, UIView(), subtitleLabel, arrowIcon + iconImageView, titleLabel, UIView(), accessoryArrowView ]) + + contentStackView = content + iconImageView.snp.makeConstraints { $0.size.equalTo(24) } - arrowIcon.snp.makeConstraints { $0.size.equalTo(16) } + accessoryArrowView.snp.makeConstraints { $0.size.equalTo(16) } roundView.addSubview(content) content.snp.makeConstraints { make in @@ -61,6 +81,12 @@ final class SettingsTableViewCell: UITableViewCell { make.bottom.top.equalToSuperview().inset(12) } + roundView.addSubview(separatorView) + separatorView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.bottom.top.equalToSuperview().inset(separatorView.strokeWidth) + } + contentView.addSubview(roundView) roundView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16) @@ -68,10 +94,8 @@ final class SettingsTableViewCell: UITableViewCell { } } - func bind(viewModel: SettingsCellViewModel) { - iconImageView.image = viewModel.icon - titleLabel.text = viewModel.title - - subtitleLabel.text = viewModel.accessoryTitle + func bind(titleViewModel: TitleIconViewModel) { + iconImageView.image = titleViewModel.icon + titleLabel.text = titleViewModel.title } } diff --git a/novawallet/Modules/Settings/View/SettingsViewLayout.swift b/novawallet/Modules/Settings/View/SettingsViewLayout.swift index 62b8dab581..aaff9b1117 100644 --- a/novawallet/Modules/Settings/View/SettingsViewLayout.swift +++ b/novawallet/Modules/Settings/View/SettingsViewLayout.swift @@ -8,7 +8,7 @@ final class SettingsViewLayout: UIView { let tableView: UITableView = { let view = UITableView(frame: .zero, style: .grouped) view.backgroundColor = R.color.colorSecondaryScreenBackground() - view.separatorColor = R.color.colorDivider() + view.separatorStyle = .none return view }() diff --git a/novawallet/Modules/Settings/ViewModel/SettingsCellViewModel.swift b/novawallet/Modules/Settings/ViewModel/SettingsCellViewModel.swift index de213bc461..9ac4fd3a54 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsCellViewModel.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsCellViewModel.swift @@ -1,8 +1,29 @@ import UIKit.UIImage struct SettingsCellViewModel { + enum Accessory { + case title(String) + case box(TitleIconViewModel) + case none + + init(optTitle: String?) { + if let title = optTitle { + self = .title(title) + } else { + self = .none + } + } + + init(optTitle: String?, icon: UIImage?) { + if let title = optTitle { + self = .box(.init(title: title, icon: icon)) + } else { + self = .none + } + } + } + let row: SettingsRow - let title: String - let icon: UIImage? - let accessoryTitle: String? + let title: TitleIconViewModel + let accessory: Accessory } diff --git a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift index 0836e0c7e1..5481cce49b 100644 --- a/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift +++ b/novawallet/Modules/Settings/ViewModel/SettingsViewModelFactory.swift @@ -81,21 +81,21 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { ) -> SettingsCellViewModel { SettingsCellViewModel( row: row, - title: row.title(for: locale), - icon: row.icon, - accessoryTitle: nil + title: .init(title: row.title(for: locale), icon: row.icon), + accessory: .none ) } private func createLanguageViewModel(from language: Language?, locale: Locale) -> SettingsCellViewModel { let title = R.string.localizable .profileLanguageTitle(preferredLanguages: locale.rLanguages) + let subtitle = language?.title(in: locale)?.capitalized + let viewModel = SettingsCellViewModel( row: .language, - title: title, - icon: SettingsRow.language.icon, - accessoryTitle: subtitle + title: .init(title: title, icon: SettingsRow.language.icon), + accessory: .init(optTitle: subtitle) ) return viewModel @@ -117,9 +117,8 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { return SettingsCellViewModel( row: row, - title: row.title(for: locale), - icon: row.icon, - accessoryTitle: subtitle + title: .init(title: row.title(for: locale), icon: row.icon), + accessory: .init(optTitle: subtitle, icon: R.image.iconConnections()) ) } @@ -130,9 +129,8 @@ final class SettingsViewModelFactory: SettingsViewModelFactoryProtocol { ) -> SettingsCellViewModel { SettingsCellViewModel( row: row, - title: row.title(for: locale), - icon: row.icon, - accessoryTitle: value + title: .init(title: row.title(for: locale), icon: row.icon), + accessory: .init(optTitle: value) ) } } diff --git a/novawallet/Modules/Vote/Governance/View/ReferendumsPersonalActivityView.swift b/novawallet/Modules/Vote/Governance/View/ReferendumsPersonalActivityView.swift index f179baaef8..28eb8b5d2c 100644 --- a/novawallet/Modules/Vote/Governance/View/ReferendumsPersonalActivityView.swift +++ b/novawallet/Modules/Vote/Governance/View/ReferendumsPersonalActivityView.swift @@ -26,7 +26,7 @@ final class ReferendumsPersonalActivityView: GenericTitleValueView< titleLabel.apply(style: .regularSubhedlinePrimary) - valueLabel.apply(style: .chipStyle) + valueLabel.apply(style: .semiboldChip) valueLabel.numberOfLines = 1 detailsLabel.apply(style: .unlockStyle) From 3b1df05c29cbc821e9aee8be982fc9e8e6873697 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 6 May 2023 11:43:11 +0500 Subject: [PATCH 30/54] fix background and add animation --- .../Sessions/View/WalletConnectSessionCell.swift | 2 ++ .../Sessions/WalletConnectSessionsViewController.swift | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift index 8480def260..c39fc384d4 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/View/WalletConnectSessionCell.swift @@ -25,6 +25,8 @@ final class WalletConnectSessionCell: PlainBaseTableViewCell 0 + + dataSource.apply(snapshot, animatingDifferences: shouldAnimate) } } From 6796921836533c3711c487cc5bf694af9cf47c47 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 6 May 2023 11:55:48 +0500 Subject: [PATCH 31/54] settings style --- .../Extension/UIKit/Style/UILabel+Style.swift | 4 ++-- .../Settings/View/SettingsBoxTableViewCell.swift | 14 +++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift index 93040d8f6e..5bdc17d459 100644 --- a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift +++ b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift @@ -36,8 +36,8 @@ extension UILabel.Style { font: .semiBoldFootnote ) - static let footnoteChip = UILabel.Style( - textColor: R.color.colorChipText(), + static let footnoteIconChip = UILabel.Style( + textColor: R.color.colorIconChip(), font: .regularFootnote ) diff --git a/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift index 0eda804017..9382240043 100644 --- a/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift +++ b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift @@ -5,27 +5,19 @@ final class SettingsBoxTableViewCell: SettingsAccessoryTableViewCell Date: Sat, 6 May 2023 13:21:09 +0500 Subject: [PATCH 32/54] adopt wallet connect scan qr code --- novawallet.xcodeproj/project.pbxproj | 4 ++ .../Extension/UIKit/Style/UILabel+Style.swift | 5 +++ .../Address/AddressScanViewFactory.swift | 1 + .../QRScanner/QRScanViewDisplayParams.swift | 8 ++++ .../QRScanner/QRScannerViewController.swift | 6 +++ .../QRScanner/QRScannerViewLayout.swift | 42 ++++++++++++------- .../QRScanner/URI/URIScanViewFactory.swift | 31 +++++++------- .../WalletConnectScanPresentable.swift | 6 ++- .../ParitySignerTxScanViewFactory.swift | 1 + .../ParitySignerScanViewFactory.swift | 1 + .../View/SettingsBoxTableViewCell.swift | 2 +- novawallet/en.lproj/Localizable.strings | 3 +- novawallet/ru.lproj/Localizable.strings | 2 + 13 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 novawallet/Common/QRScanner/QRScanViewDisplayParams.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index f132e3b412..9ce17ef29d 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1017,6 +1017,7 @@ 844C3E5A2A06141F00C4305F /* SettingsSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E592A06141F00C4305F /* SettingsSubtitleTableViewCell.swift */; }; 844C3E5C2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */; }; 844C3E5E2A06180900C4305F /* SettingsBoxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */; }; + 844C3E602A0635DE00C4305F /* QRScanViewDisplayParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5F2A0635DE00C4305F /* QRScanViewDisplayParams.swift */; }; 844CB56226F943AD00396E13 /* WalletLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */; }; 844CB56426F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */; }; 844CB56826F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */; }; @@ -4484,6 +4485,7 @@ 844C3E592A06141F00C4305F /* SettingsSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubtitleTableViewCell.swift; sourceTree = ""; }; 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccessoryTableViewCell.swift; sourceTree = ""; }; 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBoxTableViewCell.swift; sourceTree = ""; }; + 844C3E5F2A0635DE00C4305F /* QRScanViewDisplayParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanViewDisplayParams.swift; sourceTree = ""; }; 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanLocalSubscriptionFactory.swift; sourceTree = ""; }; @@ -10460,6 +10462,7 @@ 8487582827F06AF300495306 /* QRScannerViewController.swift */, 8487582F27F06AF300495306 /* Service */, 840B3D662899BFD200DA1DA9 /* QRScannerViewSettings.swift */, + 844C3E5F2A0635DE00C4305F /* QRScanViewDisplayParams.swift */, ); path = QRScanner; sourceTree = ""; @@ -18243,6 +18246,7 @@ 840874DE29782DD400ACFA55 /* GovernanceDelegateMetadataFactory.swift in Sources */, 846A835A28B8C5EC00D92892 /* TransactionExpiredPresentable.swift in Sources */, 842EBB352890A79500B952D8 /* WalletSelectionViewFactory.swift in Sources */, + 844C3E602A0635DE00C4305F /* QRScanViewDisplayParams.swift in Sources */, 8407715828CB802C007DBD24 /* ParaStkYieldBoostBaseProtocols.swift in Sources */, 84DB4E1E25E93B1700A6DF41 /* Identity.swift in Sources */, F47F5A822626FDB9009BCFF4 /* StakingPayoutViewModel.swift in Sources */, diff --git a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift index 5bdc17d459..21e6307719 100644 --- a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift +++ b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift @@ -31,6 +31,11 @@ extension UILabel.Style { font: .regularFootnote ) + static let semiboldBodyPrimary = UILabel.Style( + textColor: R.color.colorTextPrimary(), + font: .semiBoldBody + ) + static let semiboldChip = UILabel.Style( textColor: R.color.colorChipText(), font: .semiBoldFootnote diff --git a/novawallet/Common/QRScanner/Address/AddressScanViewFactory.swift b/novawallet/Common/QRScanner/Address/AddressScanViewFactory.swift index d439469aae..2d6d045c23 100644 --- a/novawallet/Common/QRScanner/Address/AddressScanViewFactory.swift +++ b/novawallet/Common/QRScanner/Address/AddressScanViewFactory.swift @@ -83,6 +83,7 @@ struct AddressScanViewFactory { let view = QRScannerViewController( title: title, + details: nil, message: message, presenter: presenter, localizationManager: localizationManager diff --git a/novawallet/Common/QRScanner/QRScanViewDisplayParams.swift b/novawallet/Common/QRScanner/QRScanViewDisplayParams.swift new file mode 100644 index 0000000000..c2f14109e8 --- /dev/null +++ b/novawallet/Common/QRScanner/QRScanViewDisplayParams.swift @@ -0,0 +1,8 @@ +import Foundation +import SoraFoundation + +struct QRScannerViewDisplayParams { + let topTitle: LocalizableResource? + let details: LocalizableResource? + let scanTitle: LocalizableResource +} diff --git a/novawallet/Common/QRScanner/QRScannerViewController.swift b/novawallet/Common/QRScanner/QRScannerViewController.swift index d25ae81b57..4de18cd213 100644 --- a/novawallet/Common/QRScanner/QRScannerViewController.swift +++ b/novawallet/Common/QRScanner/QRScannerViewController.swift @@ -7,6 +7,7 @@ class QRScannerViewController: UIViewController, ViewHolder { typealias RootViewType = QRScannerViewLayout let localizedTitle: LocalizableResource? + let localizableDetails: LocalizableResource? let localizedMessage: LocalizableResource let presenter: QRScannerPresenterProtocol let settings: QRScannerViewSettings @@ -18,12 +19,14 @@ class QRScannerViewController: UIViewController, ViewHolder { init( title: LocalizableResource?, + details: LocalizableResource?, message: LocalizableResource, presenter: QRScannerPresenterProtocol, localizationManager: LocalizationManagerProtocol, settings: QRScannerViewSettings = QRScannerViewSettings() ) { localizedTitle = title + localizableDetails = details localizedMessage = message self.presenter = presenter self.settings = settings @@ -69,6 +72,9 @@ class QRScannerViewController: UIViewController, ViewHolder { private func setupLocalization() { title = localizedTitle?.value(for: selectedLocale) rootView.titleLabel.text = localizedMessage.value(for: selectedLocale) + + rootView.detailsView.bind(viewModel: localizableDetails?.value(for: selectedLocale)) + rootView.actionButton?.imageWithTitleView?.title = R.string.localizable.qrScanUploadGallery( preferredLanguages: selectedLocale.rLanguages ) diff --git a/novawallet/Common/QRScanner/QRScannerViewLayout.swift b/novawallet/Common/QRScanner/QRScannerViewLayout.swift index 398f7f787f..76dd8e593a 100644 --- a/novawallet/Common/QRScanner/QRScannerViewLayout.swift +++ b/novawallet/Common/QRScanner/QRScannerViewLayout.swift @@ -17,23 +17,24 @@ class QRScannerViewLayout: UIView { return imageView }() - let titleLabel: UILabel = { - let label = UILabel() - label.font = .semiBoldBody - label.textColor = R.color.colorTextPrimary() - label.numberOfLines = 2 - label.textAlignment = .center - return label - }() + let titleLabel: UILabel = .create { view in + view.apply(style: .semiboldBodyPrimary) + view.numberOfLines = 2 + view.textAlignment = .center + } - let messageLabel: UILabel = { - let label = UILabel() - label.font = .semiBoldBody - label.textColor = R.color.colorTextPrimary() - label.numberOfLines = 0 - label.textAlignment = .center - return label - }() + let detailsView: IconDetailsView = .create { view in + view.detailsLabel.apply(style: .semiboldBodyPrimary) + view.detailsLabel.numberOfLines = 1 + view.iconWidth = 24 + view.spacing = 2 + } + + let messageLabel: UILabel = .create { view in + view.apply(style: .semiboldBodyPrimary) + view.numberOfLines = 0 + view.textAlignment = .center + } var actionButton: TriangularedButton? @@ -86,6 +87,15 @@ class QRScannerViewLayout: UIView { make.bottom.equalTo(qrFrameImageView.snp.top).offset(-24.0) } + addSubview(detailsView) + + detailsView.snp.makeConstraints { make in + make.leading.greaterThanOrEqualToSuperview().inset(UIConstants.horizontalInset) + make.trailing.lessThanOrEqualToSuperview().inset(UIConstants.horizontalInset) + make.centerX.equalToSuperview() + make.top.equalTo(qrFrameImageView.snp.bottom).offset(24.0) + } + if let actionButton = actionButton { addSubview(actionButton) actionButton.snp.makeConstraints { make in diff --git a/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift b/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift index 8521c12fe3..3ed4ad785b 100644 --- a/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift +++ b/novawallet/Common/QRScanner/URI/URIScanViewFactory.swift @@ -2,16 +2,19 @@ import Foundation import SoraFoundation struct URIScanViewFactory { - static func createScan( + static func createWalletConnectScan( for delegate: URIScanDelegate, context: AnyObject? ) -> QRScannerViewProtocol? { - let title = LocalizableResource { locale in - R.string.localizable.commonWalletConnect(preferredLanguages: locale.rLanguages) + let scanTitle = LocalizableResource { locale in + R.string.localizable.walletConnectScanMessage(preferredLanguages: locale.rLanguages) } - let message = LocalizableResource { locale in - R.string.localizable.walletConnectScanMessage(preferredLanguages: locale.rLanguages) + let details = LocalizableResource { locale in + let title = R.string.localizable.commonWalletConnectV2(preferredLanguages: locale.rLanguages) + let icon = R.image.iconWalletConnect()?.tinted(with: R.color.colorTextPrimary()!) + + return TitleIconViewModel(title: title, icon: icon) } let qrExtractionError = LocalizableResource { locale in @@ -20,20 +23,18 @@ struct URIScanViewFactory { return createView( matcher: SchemeURIMatcher(scheme: "wc"), - title: title, - message: message, + viewDisplayParams: .init(topTitle: nil, details: details, scanTitle: scanTitle), qrExtractionError: qrExtractionError, - for: delegate, + delegate: delegate, context: context ) } static func createView( matcher: URIQRMatching, - title: LocalizableResource, - message: LocalizableResource, + viewDisplayParams: QRScannerViewDisplayParams, qrExtractionError: LocalizableResource, - for delegate: URIScanDelegate, + delegate: URIScanDelegate, context: AnyObject? ) -> QRScannerViewProtocol? { let processingQueue = QRCaptureService.processingQueue @@ -57,10 +58,12 @@ struct URIScanViewFactory { ) let view = QRScannerViewController( - title: title, - message: message, + title: viewDisplayParams.topTitle, + details: viewDisplayParams.details, + message: viewDisplayParams.scanTitle, presenter: presenter, - localizationManager: localizationManager + localizationManager: localizationManager, + settings: .init(canUploadFromGallery: true, extendsUnderSafeArea: true) ) presenter.view = view diff --git a/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift index b7add91e32..154e1fecb2 100644 --- a/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift +++ b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectScanPresentable.swift @@ -7,11 +7,13 @@ protocol WalletConnectScanPresentable: AnyObject { extension WalletConnectScanPresentable { func showScan(from view: ControllerBackedProtocol?, delegate: URIScanDelegate) { - guard let scanView = URIScanViewFactory.createScan(for: delegate, context: nil) else { + guard let scanView = URIScanViewFactory.createWalletConnectScan(for: delegate, context: nil) else { return } - view?.controller.present(scanView.controller, animated: true) + let navigationController = NovaNavigationController(rootViewController: scanView.controller) + + view?.controller.present(navigationController, animated: true) } func hideUriScanAnimated(from view: ControllerBackedProtocol?, completion: @escaping () -> Void) { diff --git a/novawallet/Modules/ParitySigner/TransactionScan/ParitySignerTxScanViewFactory.swift b/novawallet/Modules/ParitySigner/TransactionScan/ParitySignerTxScanViewFactory.swift index 2de6d297e5..198c92577e 100644 --- a/novawallet/Modules/ParitySigner/TransactionScan/ParitySignerTxScanViewFactory.swift +++ b/novawallet/Modules/ParitySigner/TransactionScan/ParitySignerTxScanViewFactory.swift @@ -41,6 +41,7 @@ struct ParitySignerTxScanViewFactory { let view = ParitySignerTxScanViewController( title: title, + details: nil, message: message, presenter: presenter, localizationManager: LocalizationManager.shared, diff --git a/novawallet/Modules/ParitySigner/WalletScanner/ParitySignerScanViewFactory.swift b/novawallet/Modules/ParitySigner/WalletScanner/ParitySignerScanViewFactory.swift index 73e4a9f6b1..37fd4331c0 100644 --- a/novawallet/Modules/ParitySigner/WalletScanner/ParitySignerScanViewFactory.swift +++ b/novawallet/Modules/ParitySigner/WalletScanner/ParitySignerScanViewFactory.swift @@ -47,6 +47,7 @@ struct ParitySignerScanViewFactory { let view = QRScannerViewController( title: nil, + details: nil, message: message, presenter: presenter, localizationManager: localizationManager, diff --git a/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift index 9382240043..e090236507 100644 --- a/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift +++ b/novawallet/Modules/Settings/View/SettingsBoxTableViewCell.swift @@ -7,7 +7,7 @@ final class SettingsBoxTableViewCell: SettingsAccessoryTableViewCell Date: Mon, 8 May 2023 13:38:39 +0500 Subject: [PATCH 33/54] add balances store --- novawallet.xcodeproj/project.pbxproj | 100 ++++++++- .../Currency/PriceAssetInfoFactory.swift | 6 +- .../Extension/Foundation/String+split.swift | 10 + .../Common/Helpers/BalanceCalculator.swift | 187 +++++++++++++++++ novawallet/Common/Helpers/BalancesStore.swift | 194 ++++++++++++++++++ .../ScrollableContainerLayoutView.swift | 50 ++++- .../StackTable/StackWalletAmountCell.swift | 26 +++ .../WalletAccount/WalletTotalAmountView.swift | 50 +++++ .../Model/DAppAuthRequest.swift | 5 +- .../DAppMetamaskWaitingAuthState.swift | 5 +- .../DAppBrowserWaitingAuthState.swift | 5 +- .../DAppWalletAuthInteractor.swift | 9 + .../DAppWalletAuthPresenter.swift | 91 ++++++++ .../DAppWalletAuthProtocols.swift | 21 ++ .../DAppWalletAuthViewController.swift | 125 +++++++++++ .../DAppWalletAuthViewFactory.swift | 60 ++++++ .../DAppWalletAuthViewLayout.swift | 138 +++++++++++++ .../DAppWalletAuthWireframe.swift | 7 + .../ViewModel/DAppWalletAuthViewModel.swift | 16 ++ .../DAppWalletAuthViewModelFactory.swift | 135 ++++++++++++ .../DApp/Model/DAppChainsResolution.swift | 28 +++ .../DAppNetworksViewModel.swift} | 2 +- .../View/DAppNetworksViewModelFactory.swift | 36 ++++ .../WalletConnectSessionViewModel.swift | 2 +- ...WalletConnectSessionViewModelFactory.swift | 5 +- ...alletConnectSessionDetailsViewLayout.swift | 4 +- .../States/WalletConnectStateNewMessage.swift | 11 +- .../StackInfoTableCell+WallectConnect.swift | 2 +- ...alletConnectNetworksViewModelFactory.swift | 19 -- .../Common/WalletsListTableViewCell.swift | 69 +------ .../Common/WalletsListViewController.swift | 5 +- .../Common/WalletsListViewModel.swift | 4 +- .../Common/WalletsListViewModelFactory.swift | 10 +- .../Manage/WalletManageTableViewCell.swift | 29 +-- .../WalletSelectionTableViewCell.swift | 8 +- novawallet/en.lproj/Localizable.strings | 1 + novawallet/en.lproj/Localizable.stringsdict | 16 ++ novawallet/ru.lproj/Localizable.strings | 1 + novawallet/ru.lproj/Localizable.stringsdict | 16 ++ .../DAppWalletAuth/DAppWalletAuthTests.swift | 16 ++ .../DAppAuthConfirmTests.swift | 5 +- 41 files changed, 1376 insertions(+), 153 deletions(-) create mode 100644 novawallet/Common/Helpers/BalanceCalculator.swift create mode 100644 novawallet/Common/Helpers/BalancesStore.swift create mode 100644 novawallet/Common/View/StackTable/StackWalletAmountCell.swift create mode 100644 novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift create mode 100644 novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift create mode 100644 novawallet/Modules/DApp/Model/DAppChainsResolution.swift rename novawallet/Modules/DApp/{WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift => View/DAppNetworksViewModel.swift} (81%) create mode 100644 novawallet/Modules/DApp/View/DAppNetworksViewModelFactory.swift delete mode 100644 novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift create mode 100644 novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 9ce17ef29d..0b76504844 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 0CD1F4D100ED82D137AB9834 /* ParaStkStakeSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2F1EEBF48485F02BF690A4 /* ParaStkStakeSetupViewController.swift */; }; 0CE5BF4A6BC02563113DFDB8 /* GovernanceDelegateInfoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E7AAE54BF5E6A35BDD29B /* GovernanceDelegateInfoInteractor.swift */; }; 0D5245ED354CC52A842C85A0 /* TransferConfirmViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8B98AB03AAF06AA891695 /* TransferConfirmViewLayout.swift */; }; + 0D8213272889988B78188D9A /* DAppWalletAuthInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337EC62037D657258BCBC02F /* DAppWalletAuthInteractor.swift */; }; 0DAD4FE2F8B905CEBADAB3EA /* ParaStkCollatorFiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A4A258D911C67F875E386D /* ParaStkCollatorFiltersViewController.swift */; }; 0DD3DB85B0E7FD5692F58787 /* DelegationReferendumVotersPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BEE17030110A1531A231EE /* DelegationReferendumVotersPresenter.swift */; }; 0DF1E0D0CCEDC1340B7A47D7 /* TransferConfirmOnChainViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899686C7351A2600FFA08371 /* TransferConfirmOnChainViewFactory.swift */; }; @@ -88,6 +89,7 @@ 1F45D221E855D5340572C243 /* GovernanceUnavailableTracksProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59BED0E6DBC4C21D9625740C /* GovernanceUnavailableTracksProtocols.swift */; }; 1F496969FEE3E160BABDAC66 /* ReferendumVoteSetupProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5159EA2661A6CBE123CCF891 /* ReferendumVoteSetupProtocols.swift */; }; 1F88F3DBFA0BD6D0FDF558F3 /* SelectValidatorsConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975DECE71DE70DFD866B8E23 /* SelectValidatorsConfirmViewFactory.swift */; }; + 1FF9D36D42669B4C1122B533 /* DAppWalletAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */; }; 20B2942A4241F6713A1C70D9 /* StakingRewardDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2377F8FB07B47637346249F5 /* StakingRewardDetailsViewFactory.swift */; }; 211725E26764530359F53A38 /* ParitySignerTxQrInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859654B7C1FAC269CA61E71 /* ParitySignerTxQrInteractor.swift */; }; 21322B4A297840739C389F17 /* AccountManagementInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558BD7D1B8CA1409BE74879 /* AccountManagementInteractor.swift */; }; @@ -206,6 +208,7 @@ 3E480EEAF501AEB5D543506D /* UsernameSetupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172B3E9BE51A339D7A09BDA3 /* UsernameSetupPresenter.swift */; }; 3E6215E91AE1C1F78246A43C /* ParaStkUnstakeViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611AD2A7BEEEBA634F56163D /* ParaStkUnstakeViewLayout.swift */; }; 3EAB85420EDDDE7D5B03A1CF /* GovernanceUnavailableTracksWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374AF5133A7FD39B961B9C84 /* GovernanceUnavailableTracksWireframe.swift */; }; + 3F3AE7490C59A0CE0BF2D7A7 /* DAppWalletAuthViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A5B4E6779AA27F10713C6 /* DAppWalletAuthViewFactory.swift */; }; 3F7F10D0E1BDE09CBE64BD2D /* CrowdloanYourContributionsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC155A29FA8777D90A46913D /* CrowdloanYourContributionsViewFactory.swift */; }; 3FC436AED4098456EDEAF484 /* MessageSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A2F34329579E12A2836E77 /* MessageSheetViewController.swift */; }; 3FF8EE1158A273D0D50BC7A6 /* StakingUnbondConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45B7031E0809CED062C83F8 /* StakingUnbondConfirmPresenter.swift */; }; @@ -371,6 +374,7 @@ 75249684C6F3EE4E553DABA1 /* GovernanceYourDelegationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21621DCC39234CC4B0A1433B /* GovernanceYourDelegationsPresenter.swift */; }; 7580D432F22904C8F71441FE /* ParitySignerTxScanInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FEB9C65F32B7A4FD27C9EB /* ParitySignerTxScanInteractor.swift */; }; 7584B6DC2C7F8B2B6671908F /* ParaStkYieldBoostStopViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF58EC3A44E4DDDFB4B5C84 /* ParaStkYieldBoostStopViewLayout.swift */; }; + 75B99AA8D71430BACFAEF755 /* DAppWalletAuthWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03622BA8CF4657307B8F9B97 /* DAppWalletAuthWireframe.swift */; }; 75DAB313623E900EC475E215 /* LedgerTxConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCBCB7C3ABB6C06CD4681D44 /* LedgerTxConfirmViewFactory.swift */; }; 75E689BC8D16786DF2674171 /* AssetListViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F4883B898928C77D17C824 /* AssetListViewLayout.swift */; }; 766FE2FAB8509BF0F56EA3C0 /* ParaStkCollatorInfoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3B8502E5BF8CDD7ACE2DD0 /* ParaStkCollatorInfoProtocols.swift */; }; @@ -487,7 +491,7 @@ 840B3D70289A575A00DA1DA9 /* ParitySignerScanProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840B3D6F289A575A00DA1DA9 /* ParitySignerScanProtocols.swift */; }; 840BF22526C2653100E3A955 /* ChainSyncServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BF22426C2653100E3A955 /* ChainSyncServiceTests.swift */; }; 840BF22726C2C8A600E3A955 /* ChainSyncEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BF22626C2C8A600E3A955 /* ChainSyncEvents.swift */; }; - 840D55972A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */; }; + 840D55972A0513CF0025D91C /* DAppNetworksViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D55962A0513CF0025D91C /* DAppNetworksViewModelFactory.swift */; }; 840D626F29CB39EE00D5E894 /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D626E29CB39EE00D5E894 /* URLBuilder.swift */; }; 840D627129CB3FD900D5E894 /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D627029CB3FD900D5E894 /* URLBuilderTests.swift */; }; 840D627529CB46E900D5E894 /* ConnectionApiKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D627429CB46E900D5E894 /* ConnectionApiKeys.swift */; }; @@ -1018,6 +1022,13 @@ 844C3E5C2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */; }; 844C3E5E2A06180900C4305F /* SettingsBoxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */; }; 844C3E602A0635DE00C4305F /* QRScanViewDisplayParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E5F2A0635DE00C4305F /* QRScanViewDisplayParams.swift */; }; + 844C3E622A0648AC00C4305F /* DAppChainsResolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E612A0648AC00C4305F /* DAppChainsResolution.swift */; }; + 844C3E652A07627E00C4305F /* DAppWalletAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E642A07627E00C4305F /* DAppWalletAuthViewModel.swift */; }; + 844C3E672A0763D500C4305F /* WalletTotalAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E662A0763D500C4305F /* WalletTotalAmountView.swift */; }; + 844C3E692A07BC4500C4305F /* StackWalletAmountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E682A07BC4500C4305F /* StackWalletAmountCell.swift */; }; + 844C3E6B2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6A2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift */; }; + 844C3E6D2A08E1B300C4305F /* BalancesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */; }; + 844C3E6F2A08E74D00C4305F /* BalanceCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */; }; 844CB56226F943AD00396E13 /* WalletLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */; }; 844CB56426F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */; }; 844CB56826F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */; }; @@ -2221,7 +2232,7 @@ 84D184EA2A04D9980060C1BD /* StackStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184E92A04D9980060C1BD /* StackStatusCell.swift */; }; 84D184EC2A04DA810060C1BD /* GlowingStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184EB2A04DA810060C1BD /* GlowingStatusView.swift */; }; 84D184EF2A04E2760060C1BD /* WalletConnectSessionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184EE2A04E2760060C1BD /* WalletConnectSessionViewModel.swift */; }; - 84D184F12A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */; }; + 84D184F12A04EF6D0060C1BD /* DAppNetworksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F02A04EF6D0060C1BD /* DAppNetworksViewModel.swift */; }; 84D184F42A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */; }; 84D184F62A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */; }; 84D184F82A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D184F72A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift */; }; @@ -2952,6 +2963,7 @@ A2F7908210A0398EDBBA89BD /* NftDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3CC9CAB00B604CD1AC5B4D8 /* NftDetailsViewFactory.swift */; }; A32E1373E3671D518FFC3BC2 /* YourValidatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90CEC70F101AA25A4C00021 /* YourValidatorListViewController.swift */; }; A3BDFA01A32B6C7463E6EFFA /* GovernanceUnlockConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513A449CCF5A417B67B7067D /* GovernanceUnlockConfirmPresenter.swift */; }; + A5153C322938579FA145742A /* DAppWalletAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B675B6727754E3727CBC7BE /* DAppWalletAuthViewController.swift */; }; A5880E3789BC9E30835BDCC7 /* TransferSetupViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8202B83B2DF36439CB6449C6 /* TransferSetupViewFactory.swift */; }; A6E762549FF85393D1B69007 /* GovernanceSelectTracksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984A42A14D86FC79DA777833 /* GovernanceSelectTracksPresenter.swift */; }; A714CEAF7A86292E8D679056 /* ParaStkStakeSetupViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7955BBDC93BC07D069B8F /* ParaStkStakeSetupViewFactory.swift */; }; @@ -3104,6 +3116,7 @@ B317AB093D99677D292121C4 /* YourWalletsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ED3A7899C88876AB3DCA5F /* YourWalletsViewController.swift */; }; B3E567D46F54E8E735792FE1 /* ParaStkYieldBoostSetupViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8958927F940B9B638EF89D /* ParaStkYieldBoostSetupViewFactory.swift */; }; B409644ED1E20062A3EA0316 /* DAppTxDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BCDAB970C17F9798AC79B08 /* DAppTxDetailsViewController.swift */; }; + B42652A7AB4244F19473C7C5 /* DAppWalletAuthProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDC898FE48131F4FB8E64B8 /* DAppWalletAuthProtocols.swift */; }; B51AD1836313CE26F369ED3F /* CustomValidatorListWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D540DFC00C25D8F73CFDC3 /* CustomValidatorListWireframe.swift */; }; B61457C5248F3B0E88A7990E /* ParaStkRebondPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F49B3B261FBA0B568A5320 /* ParaStkRebondPresenter.swift */; }; B6D4C073B3F984FE0348B7D4 /* ParaStkYieldBoostStartInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0988D8EC0768B03BA7C55612 /* ParaStkYieldBoostStartInteractor.swift */; }; @@ -3201,6 +3214,7 @@ D72773CE9AF638387DA9BA77 /* MoonbeamTermsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA086DFCCA0976489FD38B95 /* MoonbeamTermsInteractor.swift */; }; D76DC92333E4D689934D47EB /* TokensAddSelectNetworkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BBECA4E3F54769D95DD21E /* TokensAddSelectNetworkViewController.swift */; }; D7B4AE688B93EC562F452F4E /* ParaStkUnstakeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76AA6A6232B1CF2D5AF74D0D /* ParaStkUnstakeInteractor.swift */; }; + D7D91A2CACE6FE12AE634BEF /* DAppWalletAuthViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16A05911AC400226DD29AE20 /* DAppWalletAuthViewLayout.swift */; }; D83B47B07C0D40A327AC44F7 /* CustomValidatorListViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52B8815D6AF5E69B145D245 /* CustomValidatorListViewFactory.swift */; }; D840B64C33EF47E723905378 /* OperationDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F05632A6635A54A9CDA7FC /* OperationDetailsViewFactory.swift */; }; D8581E5440A19D977E17BFDE /* StakingAmountViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D54DC9992CF9CB6699AA3 /* StakingAmountViewFactory.swift */; }; @@ -3253,6 +3267,7 @@ E4021A6E90432CC5C797A647 /* ReferendumVotersWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FBF368FBB46AD4DE606DB1 /* ReferendumVotersWireframe.swift */; }; E477B09B47A3021EF1CE66F0 /* ParaStkRedeemViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D3F02FD57B4C28BA09C5F8 /* ParaStkRedeemViewLayout.swift */; }; E488F3E052650FF525D41D63 /* LedgerTxConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B89948DC631DE61E01007 /* LedgerTxConfirmInteractor.swift */; }; + E5528BE6A0BD2D2C53C6F5F8 /* DAppWalletAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C56F2A76BA8745F1F708D4 /* DAppWalletAuthPresenter.swift */; }; E5DC2660D78D3CC9FC48E748 /* LedgerAccountConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A04CDB05A013ED57D3DEA3 /* LedgerAccountConfirmationViewController.swift */; }; E5F3DF66415E54AE04D0C9A9 /* StakingMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A05EB4FAF2FDE7DECEA93E4 /* StakingMainViewController.swift */; }; E65FDA8BF9DBE7F50AE9D733 /* ChangeWatchOnlyWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E31F4AE945AF2DE98539C /* ChangeWatchOnlyWireframe.swift */; }; @@ -3468,6 +3483,7 @@ 024B7E67C0603C53981EC394 /* ReferendumVoteSetupInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteSetupInteractor.swift; sourceTree = ""; }; 02ACCC85B2CCF3D9392CA9B4 /* CrowdloanListProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanListProtocols.swift; sourceTree = ""; }; 02D8F02830944DBAF72D8A41 /* MoonbeamTermsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoonbeamTermsProtocols.swift; sourceTree = ""; }; + 03622BA8CF4657307B8F9B97 /* DAppWalletAuthWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthWireframe.swift; sourceTree = ""; }; 037D3CE23FFD176F4F7DABC0 /* ParaStkStakeConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeConfirmViewFactory.swift; sourceTree = ""; }; 038B1C35376B62CFE28C403D /* ParaStkCollatorFiltersWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkCollatorFiltersWireframe.swift; sourceTree = ""; }; 03E6ED849A247ACCC3FB1AC3 /* TokensManageAddViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensManageAddViewController.swift; sourceTree = ""; }; @@ -3515,6 +3531,7 @@ 15A4DFCC4236D25A3D59F809 /* DAppBrowserInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserInteractor.swift; sourceTree = ""; }; 15E09DD01C1CC61EA5CDED9C /* InAppUpdatesInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InAppUpdatesInteractor.swift; sourceTree = ""; }; 15E1472817503D3FB44BDC46 /* GovernanceDelegateSetupInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateSetupInteractor.swift; sourceTree = ""; }; + 16A05911AC400226DD29AE20 /* DAppWalletAuthViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewLayout.swift; sourceTree = ""; }; 172A76B6B50406CEA464FAC6 /* GovernanceDelegateConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateConfirmProtocols.swift; sourceTree = ""; }; 172B3E9BE51A339D7A09BDA3 /* UsernameSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UsernameSetupPresenter.swift; sourceTree = ""; }; 1754AB91CF4744E49D1310AE /* ChangeWatchOnlyPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChangeWatchOnlyPresenter.swift; sourceTree = ""; }; @@ -3542,6 +3559,7 @@ 1DECC58C93DB18E79A03B5A0 /* AssetSelectionProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetSelectionProtocols.swift; sourceTree = ""; }; 1E067006C1BC9DFCA5E8DB86 /* GovernanceUnlockConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceUnlockConfirmWireframe.swift; sourceTree = ""; }; 1E5CB64B91B35804B3671456 /* ControllerAccountPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountPresenter.swift; sourceTree = ""; }; + 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthTests.swift; sourceTree = ""; }; 1F3A05E0F46351784030D1AA /* ChainAddressDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsPresenter.swift; sourceTree = ""; }; 1F7865BACFB8591F67D8EE06 /* TransferConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransferConfirmProtocols.swift; sourceTree = ""; }; 1FF860B3465854DCBC02DFB3 /* DAppBrowserPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserPresenter.swift; sourceTree = ""; }; @@ -3626,6 +3644,7 @@ 32A5AB0D58B70774D1350AF9 /* AddDelegationInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddDelegationInteractor.swift; sourceTree = ""; }; 335E8C17DCB794733476AAE3 /* ParaStkStakeConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeConfirmViewController.swift; sourceTree = ""; }; 336395FFC4B2104A9651A2DE /* StakingRewardPayoutsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardPayoutsViewFactory.swift; sourceTree = ""; }; + 337EC62037D657258BCBC02F /* DAppWalletAuthInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthInteractor.swift; sourceTree = ""; }; 33DFAA0EEEA7F99C6D1CF4B1 /* ReferendumFullDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumFullDetailsPresenter.swift; sourceTree = ""; }; 340D78F99A4FCDC04C012ED3 /* StakingRebagConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRebagConfirmInteractor.swift; sourceTree = ""; }; 3531B386DEF40108C34E7232 /* ReferendumVoteConfirmViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteConfirmViewLayout.swift; sourceTree = ""; }; @@ -3700,6 +3719,7 @@ 470B64C45E547C25FCCCFC33 /* StakingMainProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingMainProtocols.swift; sourceTree = ""; }; 475B5A735FE818842CB3C82C /* AssetsSearchViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetsSearchViewFactory.swift; sourceTree = ""; }; 47759907380BE9300E54DC78 /* ExportMnemonicViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicViewFactory.swift; sourceTree = ""; }; + 47C56F2A76BA8745F1F708D4 /* DAppWalletAuthPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthPresenter.swift; sourceTree = ""; }; 4809C2DB0C15A9B2890C1AC6 /* LedgerPerformOperationInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerPerformOperationInteractor.swift; sourceTree = ""; }; 48182DE3A1302757558031FD /* TokenManageSinglePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokenManageSinglePresenter.swift; sourceTree = ""; }; 48C158C8D1855BCE53636934 /* AccountCreateProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountCreateProtocols.swift; sourceTree = ""; }; @@ -3762,6 +3782,7 @@ 5AA3BF0C9C1E0E2C67D962F5 /* PurchaseViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PurchaseViewFactory.swift; sourceTree = ""; }; 5AD16D5FA4115F2A525BDE4F /* ParaStkRedeemViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkRedeemViewController.swift; sourceTree = ""; }; 5AFA57BC935878DEBBE32EEA /* GovernanceRevokeDelegationConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceRevokeDelegationConfirmInteractor.swift; sourceTree = ""; }; + 5B675B6727754E3727CBC7BE /* DAppWalletAuthViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewController.swift; sourceTree = ""; }; 5B8B0940B2CB25AD9C36206E /* SelectValidatorsConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectValidatorsConfirmViewController.swift; sourceTree = ""; }; 5BA3C305F49A4E609C7A5C14 /* GovernanceDelegateConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateConfirmViewController.swift; sourceTree = ""; }; 5BC9AC96424DB8AB8038ED59 /* WalletConnectPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectPresenter.swift; sourceTree = ""; }; @@ -3811,6 +3832,7 @@ 6B60728FCFBC8A9BE4C7B50B /* YourValidatorListInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourValidatorListInteractor.swift; sourceTree = ""; }; 6BA14E9659FC44A438FFEEB5 /* ParitySignerTxQrWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParitySignerTxQrWireframe.swift; sourceTree = ""; }; 6C3AD523C06AD4FD58B7B3CC /* MessageSheetPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MessageSheetPresenter.swift; sourceTree = ""; }; + 6CDC898FE48131F4FB8E64B8 /* DAppWalletAuthProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthProtocols.swift; sourceTree = ""; }; 6D9A2CF1016F4D5A7F075B69 /* GovernanceSelectTracksInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceSelectTracksInteractor.swift; sourceTree = ""; }; 6DEC8CCF0671304A658AD606 /* LedgerWalletAccountConfirmationWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerWalletAccountConfirmationWireframe.swift; sourceTree = ""; }; 6E2509FEA85677165C4CCCFF /* YourWalletsViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourWalletsViewLayout.swift; sourceTree = ""; }; @@ -3947,7 +3969,7 @@ 840B3D6F289A575A00DA1DA9 /* ParitySignerScanProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParitySignerScanProtocols.swift; sourceTree = ""; }; 840BF22426C2653100E3A955 /* ChainSyncServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainSyncServiceTests.swift; sourceTree = ""; }; 840BF22626C2C8A600E3A955 /* ChainSyncEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainSyncEvents.swift; sourceTree = ""; }; - 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectNetworksViewModelFactory.swift; sourceTree = ""; }; + 840D55962A0513CF0025D91C /* DAppNetworksViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppNetworksViewModelFactory.swift; sourceTree = ""; }; 840D626D29CB32F100D5E894 /* SubstrateDataModel12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SubstrateDataModel12.xcdatamodel; sourceTree = ""; }; 840D626E29CB39EE00D5E894 /* URLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilder.swift; sourceTree = ""; }; 840D627029CB3FD900D5E894 /* URLBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilderTests.swift; sourceTree = ""; }; @@ -4486,6 +4508,13 @@ 844C3E5B2A0615A200C4305F /* SettingsAccessoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccessoryTableViewCell.swift; sourceTree = ""; }; 844C3E5D2A06180900C4305F /* SettingsBoxTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBoxTableViewCell.swift; sourceTree = ""; }; 844C3E5F2A0635DE00C4305F /* QRScanViewDisplayParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanViewDisplayParams.swift; sourceTree = ""; }; + 844C3E612A0648AC00C4305F /* DAppChainsResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppChainsResolution.swift; sourceTree = ""; }; + 844C3E642A07627E00C4305F /* DAppWalletAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewModel.swift; sourceTree = ""; }; + 844C3E662A0763D500C4305F /* WalletTotalAmountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTotalAmountView.swift; sourceTree = ""; }; + 844C3E682A07BC4500C4305F /* StackWalletAmountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackWalletAmountCell.swift; sourceTree = ""; }; + 844C3E6A2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewModelFactory.swift; sourceTree = ""; }; + 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalancesStore.swift; sourceTree = ""; }; + 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceCalculator.swift; sourceTree = ""; }; 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanLocalSubscriptionFactory.swift; sourceTree = ""; }; @@ -5707,7 +5736,7 @@ 84D184E92A04D9980060C1BD /* StackStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackStatusCell.swift; sourceTree = ""; }; 84D184EB2A04DA810060C1BD /* GlowingStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlowingStatusView.swift; sourceTree = ""; }; 84D184EE2A04E2760060C1BD /* WalletConnectSessionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionViewModel.swift; sourceTree = ""; }; - 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectNetworksViewModel.swift; sourceTree = ""; }; + 84D184F02A04EF6D0060C1BD /* DAppNetworksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppNetworksViewModel.swift; sourceTree = ""; }; 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackInfoTableCell+WallectConnect.swift"; sourceTree = ""; }; 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackStatusCell+WalletConnect.swift"; sourceTree = ""; }; 84D184F72A04F9D60060C1BD /* WalletConnectSessionViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionViewModelFactory.swift; sourceTree = ""; }; @@ -6727,6 +6756,7 @@ DD1A2F7E5E278FDCC89FE097 /* OnChainTransferSetupPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OnChainTransferSetupPresenter.swift; sourceTree = ""; }; DD1A35F4D82F97C9663F1CD4 /* ReferendumVoteConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteConfirmInteractor.swift; sourceTree = ""; }; DD2F1EEBF48485F02BF690A4 /* ParaStkStakeSetupViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupViewController.swift; sourceTree = ""; }; + DD4A5B4E6779AA27F10713C6 /* DAppWalletAuthViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewFactory.swift; sourceTree = ""; }; DDB1A69B90DCA16C219BF087 /* GovernanceDelegateInfoPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceDelegateInfoPresenter.swift; sourceTree = ""; }; DDF3C1CFECE4340E82837FC4 /* ReferendumOnChainVotersViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumOnChainVotersViewFactory.swift; sourceTree = ""; }; DE767858B6CF5F6F7C7B418E /* ReferendumVoteConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteConfirmProtocols.swift; sourceTree = ""; }; @@ -7411,6 +7441,14 @@ path = Confirm; sourceTree = ""; }; + 312F85F94736E09922F5733F /* DAppWalletAuth */ = { + isa = PBXGroup; + children = ( + 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */, + ); + path = DAppWalletAuth; + sourceTree = ""; + }; 33419BD574C1318B6848B556 /* DAppOperationConfirm */ = { isa = PBXGroup; children = ( @@ -9311,6 +9349,15 @@ path = AutomationTime; sourceTree = ""; }; + 844C3E632A07626600C4305F /* ViewModel */ = { + isa = PBXGroup; + children = ( + 844C3E642A07627E00C4305F /* DAppWalletAuthViewModel.swift */, + 844C3E6A2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 844D228F25EE5CB700C022F7 /* DataProviders */ = { isa = PBXGroup; children = ( @@ -10028,6 +10075,7 @@ E06E44775A3129B62D29EC6E /* DAppList */, 33419BD574C1318B6848B556 /* DAppOperationConfirm */, 9482C532739CF72E0D6C96E3 /* DAppAuthConfirm */, + E530843F8B8FC936EF07C08F /* DAppWalletAuth */, 54CA53FE46396489FDE213E0 /* DAppPhishing */, 1673C6B5E37788C92D8E37D4 /* DAppAddFavorite */, 17C8516C4BEF61B6DE078C60 /* DAppAuthSettings */, @@ -10117,6 +10165,7 @@ 849D14C92994D9BC0048E947 /* StackIconTitleValueCell.swift */, 843125CC299A71B20063745B /* StackButtonsCell.swift */, 84D184E92A04D9980060C1BD /* StackStatusCell.swift */, + 844C3E682A07BC4500C4305F /* StackWalletAmountCell.swift */, ); path = StackTable; sourceTree = ""; @@ -11209,6 +11258,8 @@ 840D626E29CB39EE00D5E894 /* URLBuilder.swift */, 840D627629CB487100D5E894 /* EnviromentVariables.swift */, 843F9ABB29DDAF8F004F1737 /* JSONRPCTimeout.swift */, + 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */, + 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */, ); path = Helpers; sourceTree = ""; @@ -12336,6 +12387,7 @@ 84B7C705289BFA79001A3566 /* AccountManagement */, 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, + 312F85F94736E09922F5733F /* DAppWalletAuth */, ); path = Modules; sourceTree = ""; @@ -13253,6 +13305,7 @@ 84350ACD2845709D0031EF24 /* IdentityAccountView.swift */, 84350ACF2845724E0031EF24 /* IdentityAccountInfoView.swift */, 84BC7048289F10AE008A9758 /* WalletAccountInfoView+Style.swift */, + 844C3E662A0763D500C4305F /* WalletTotalAmountView.swift */, ); path = WalletAccount; sourceTree = ""; @@ -13533,10 +13586,8 @@ 84D184F22A04EF740060C1BD /* ViewModel */ = { isa = PBXGroup; children = ( - 84D184F02A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift */, 84D184F32A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift */, 84D184F52A04F5C10060C1BD /* StackStatusCell+WalletConnect.swift */, - 840D55962A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift */, ); path = ViewModel; sourceTree = ""; @@ -13554,6 +13605,8 @@ isa = PBXGroup; children = ( 84D2F1A4277437D30040C680 /* DAppIconView.swift */, + 840D55962A0513CF0025D91C /* DAppNetworksViewModelFactory.swift */, + 84D184F02A04EF6D0060C1BD /* DAppNetworksViewModel.swift */, ); path = View; sourceTree = ""; @@ -14062,6 +14115,7 @@ 84D17ED728053E3200F7BAFF /* DAppFavoriteMapper.swift */, 8804AD8C295B7735001C4E09 /* DAppGlobalSettingsMapper.swift */, 84E8BA0629FE888C00FD9F40 /* DAppUnknownChain.swift */, + 844C3E612A0648AC00C4305F /* DAppChainsResolution.swift */, ); path = Model; sourceTree = ""; @@ -16032,6 +16086,21 @@ path = NetworkSelection; sourceTree = ""; }; + E530843F8B8FC936EF07C08F /* DAppWalletAuth */ = { + isa = PBXGroup; + children = ( + 844C3E632A07626600C4305F /* ViewModel */, + 6CDC898FE48131F4FB8E64B8 /* DAppWalletAuthProtocols.swift */, + 03622BA8CF4657307B8F9B97 /* DAppWalletAuthWireframe.swift */, + 47C56F2A76BA8745F1F708D4 /* DAppWalletAuthPresenter.swift */, + 337EC62037D657258BCBC02F /* DAppWalletAuthInteractor.swift */, + 5B675B6727754E3727CBC7BE /* DAppWalletAuthViewController.swift */, + 16A05911AC400226DD29AE20 /* DAppWalletAuthViewLayout.swift */, + DD4A5B4E6779AA27F10713C6 /* DAppWalletAuthViewFactory.swift */, + ); + path = DAppWalletAuth; + sourceTree = ""; + }; E8A61B9F062344AB1C1A7C32 /* AddDelegation */ = { isa = PBXGroup; children = ( @@ -17217,6 +17286,7 @@ 8490141724A92F6D008F705E /* OnboardingMainWireframe.swift in Sources */, 84953F72293630080033F47D /* AssetHistoryServiceType.swift in Sources */, 848841C128D1229000D750E9 /* ParaStkYieldBoostStopError.swift in Sources */, + 844C3E672A0763D500C4305F /* WalletTotalAmountView.swift in Sources */, 8425EB1B25EADD8600C307C9 /* StakingConstants.swift in Sources */, 84690783264132AB0030E693 /* WithdrawUnbondedCall.swift in Sources */, 84F0316F27302888003BDCF8 /* CoingeckoPriceListSource.swift in Sources */, @@ -17285,6 +17355,7 @@ 8490151824AB8C6D008F705E /* WalletCommonStyleConfigurator.swift in Sources */, 849E0CD025CFDDB700B33506 /* StorageUpdateData+Decoding.swift in Sources */, 884A752D299B56F200FEFC30 /* DelegateReferendumsModelFactoryProtocol.swift in Sources */, + 844C3E6D2A08E1B300C4305F /* BalancesStore.swift in Sources */, 8430AAE126022CA1005B1066 /* BaseStakingState.swift in Sources */, 8490141124A92F6D008F705E /* OnboardingMainViewController.swift in Sources */, D9D163642744F60D00681C1F /* ExternalContribution.swift in Sources */, @@ -17598,6 +17669,7 @@ 84490E7427F2CE2C00941837 /* TransferMetadataContext.swift in Sources */, 8490151324AB8A3A008F705E /* WalletEmptyStateDataSource.swift in Sources */, 88F19DE028D8D0F600F6E459 /* LoadableViewModelState+Addition.swift in Sources */, + 844C3E652A07627E00C4305F /* DAppWalletAuthViewModel.swift in Sources */, 84A3B8A22836DA2600DE2669 /* LastAccountIdKeyWrapper.swift in Sources */, 847A258E29B5D25E0054F90C /* GovJsonLocalStorageSubscriber.swift in Sources */, 84EDF67329C66015002173E6 /* EvmNativeTransactionHistoryUpdater.swift in Sources */, @@ -18327,6 +18399,7 @@ 84E63C1728FFC69A0093534A /* DiscreteGradientSlider.swift in Sources */, 84EC2D18276B9DBC009B0BE1 /* PolkadotExtensionAccount.swift in Sources */, 84FC190D29B7EE1600BCCAA5 /* MultiExtrinsicFeeProxy.swift in Sources */, + 844C3E6F2A08E74D00C4305F /* BalanceCalculator.swift in Sources */, 8814774029B07B66001E98A1 /* DelegateGroupActionTitleControl.swift in Sources */, F4E17FCF272182E800FE36D3 /* MoonbeamAgreeRemarkRequest.swift in Sources */, 841221A228F0520300715C82 /* GovMetadataLocalStorageHandler.swift in Sources */, @@ -18533,7 +18606,7 @@ 84C2F27725E296CD0050A4AD /* RewardDestinationViewModelFactory.swift in Sources */, 84D97EC82520D32000F07405 /* PolkadotIcon+Image.swift in Sources */, 84C2802126F541DE006E8014 /* WebSocketEngine+Connection.swift in Sources */, - 840D55972A0513CF0025D91C /* WalletConnectNetworksViewModelFactory.swift in Sources */, + 840D55972A0513CF0025D91C /* DAppNetworksViewModelFactory.swift in Sources */, 84CA68D326BE9A35003B9453 /* RuntimeProvider.swift in Sources */, 8401620625E130D60087A5F3 /* ViewAction.swift in Sources */, 84A2C90424E07F400020D3B7 /* AccountOperationFactoryError.swift in Sources */, @@ -18630,6 +18703,7 @@ E14F809C3917EFA4B5388AC8 /* AccountConfirmViewFactory.swift in Sources */, 8804AD8B295B76E5001C4E09 /* DAppGlobalSettings.swift in Sources */, 887C450229AC7FD900950F98 /* DelegationReferendumVotersError.swift in Sources */, + 844C3E622A0648AC00C4305F /* DAppChainsResolution.swift in Sources */, 842A738027DCD427006EE1EA /* OperationDetailsRewardView.swift in Sources */, 9D5926790B055C56FB74B282 /* AccountManagementProtocols.swift in Sources */, 848EAEB02659310A00676CEA /* CrowdloanStatus.swift in Sources */, @@ -19071,7 +19145,7 @@ B071927DF8DD5C3CA84494BA /* RecommendedValidatorListViewController.swift in Sources */, D6511F7C3E55197F82AB552C /* RecommendedValidatorListViewFactory.swift in Sources */, C7D77690E10875CF1856EBA1 /* StakingRewardPayoutsProtocols.swift in Sources */, - 84D184F12A04EF6D0060C1BD /* WalletConnectNetworksViewModel.swift in Sources */, + 84D184F12A04EF6D0060C1BD /* DAppNetworksViewModel.swift in Sources */, 88A0E0FF28A284C700A9C940 /* SelectedCurrencyDepending.swift in Sources */, 2918DCAEB91F8276446873C8 /* StakingRewardPayoutsWireframe.swift in Sources */, AB5E2A2B4CC823E6F6515ADD /* StakingRewardPayoutsPresenter.swift in Sources */, @@ -19982,6 +20056,7 @@ D5BB3A36DB9ADD25EE43109F /* GovernanceUnlockConfirmViewFactory.swift in Sources */, 58F385F41D42CC96373EDA42 /* TokensManageProtocols.swift in Sources */, CA3C4729115D875D0C80A3E8 /* TokensManageWireframe.swift in Sources */, + 844C3E6B2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift in Sources */, 88E5E2A7295D8FA1001B1D41 /* TitleIconViewModel+Hashable.swift in Sources */, 1772735F89EFA931DF7420AD /* TokensManagePresenter.swift in Sources */, 363F8BBFDE9BF834BDEC346E /* TokensManageInteractor.swift in Sources */, @@ -20116,6 +20191,7 @@ 7D3A5862F4A8F757A091F422 /* GovernanceRevokeDelegationConfirmInteractor.swift in Sources */, BAF044619C83E31EEEDA0BCB /* GovernanceRevokeDelegationConfirmViewController.swift in Sources */, 33991FF16853D0F885C520A5 /* GovernanceRevokeDelegationConfirmViewLayout.swift in Sources */, + 844C3E692A07BC4500C4305F /* StackWalletAmountCell.swift in Sources */, C29BA2F7EBAFED5D118E0C0C /* GovernanceRevokeDelegationConfirmViewFactory.swift in Sources */, 642FDAC7A745ECC92BFF0CC0 /* DelegationReferendumVotersProtocols.swift in Sources */, AD877F7F77F9CB862DC7D5B3 /* DelegationReferendumVotersWireframe.swift in Sources */, @@ -20157,6 +20233,13 @@ E06F3BD43E589BCE3904BBCB /* WalletConnectSessionDetailsViewController.swift in Sources */, FFF7CC228C1EB14B8677BD17 /* WalletConnectSessionDetailsViewLayout.swift in Sources */, 54983C354F7EDCD8014C8371 /* WalletConnectSessionDetailsViewFactory.swift in Sources */, + B42652A7AB4244F19473C7C5 /* DAppWalletAuthProtocols.swift in Sources */, + 75B99AA8D71430BACFAEF755 /* DAppWalletAuthWireframe.swift in Sources */, + E5528BE6A0BD2D2C53C6F5F8 /* DAppWalletAuthPresenter.swift in Sources */, + 0D8213272889988B78188D9A /* DAppWalletAuthInteractor.swift in Sources */, + A5153C322938579FA145742A /* DAppWalletAuthViewController.swift in Sources */, + D7D91A2CACE6FE12AE634BEF /* DAppWalletAuthViewLayout.swift in Sources */, + 3F3AE7490C59A0CE0BF2D7A7 /* DAppWalletAuthViewFactory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -20317,6 +20400,7 @@ 84B7C748289BFA79001A3566 /* WalletListTests.swift in Sources */, 84B7C720289BFA79001A3566 /* ReferralCrowdloanTests.swift in Sources */, F4897BB126AED13D0075F291 /* EraCountdownOperationFactoryStub.swift in Sources */, + 1FF9D36D42669B4C1122B533 /* DAppWalletAuthTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawallet/Common/Currency/PriceAssetInfoFactory.swift b/novawallet/Common/Currency/PriceAssetInfoFactory.swift index ecf7ff1a21..01d402c15e 100644 --- a/novawallet/Common/Currency/PriceAssetInfoFactory.swift +++ b/novawallet/Common/Currency/PriceAssetInfoFactory.swift @@ -1,5 +1,5 @@ protocol PriceAssetInfoFactoryProtocol { - func createAssetBalanceDisplayInfo(from currencyId: Int?) -> AssetBalanceDisplayInfo + func createAssetBalanceDisplayInfo(from priceId: Int?) -> AssetBalanceDisplayInfo } final class PriceAssetInfoFactory: PriceAssetInfoFactoryProtocol { @@ -9,8 +9,8 @@ final class PriceAssetInfoFactory: PriceAssetInfoFactoryProtocol { self.currencyManager = currencyManager } - func createAssetBalanceDisplayInfo(from _: Int?) -> AssetBalanceDisplayInfo { - let mappedCurrencyId = currencyManager.selectedCurrency.id + func createAssetBalanceDisplayInfo(from priceId: Int?) -> AssetBalanceDisplayInfo { + let mappedCurrencyId = priceId ?? currencyManager.selectedCurrency.id guard let currency = currencyManager.availableCurrencies.first(where: { $0.id == mappedCurrencyId }) else { assertionFailure("Currency with id: \(mappedCurrencyId) not found") return .usd() diff --git a/novawallet/Common/Extension/Foundation/String+split.swift b/novawallet/Common/Extension/Foundation/String+split.swift index 51fb1cfa1e..48b4ea92e0 100644 --- a/novawallet/Common/Extension/Foundation/String+split.swift +++ b/novawallet/Common/Extension/Foundation/String+split.swift @@ -5,7 +5,17 @@ extension String { case hashtag = "#" } + enum CompoundSeparator: String { + case commaSpace = ", " + } + func split(by separator: Separator, maxSplits: Int = .max) -> [String] { split(separator: separator.rawValue, maxSplits: maxSplits).map { String($0) } } } + +extension Array where Array.Element == String { + func joined(with separator: String.CompoundSeparator) -> String { + joined(separator: separator.rawValue) + } +} diff --git a/novawallet/Common/Helpers/BalanceCalculator.swift b/novawallet/Common/Helpers/BalanceCalculator.swift new file mode 100644 index 0000000000..ee5596ea5e --- /dev/null +++ b/novawallet/Common/Helpers/BalanceCalculator.swift @@ -0,0 +1,187 @@ +import Foundation +import RobinHood +import BigInt + +protocol BalancesCalculating: AnyObject { + func calculateTotalValue(for wallet: MetaAccountModel) -> Decimal +} + +final class BalancesCalculator { + private var identifierMapping: [String: AssetBalanceId] = [:] + private var balances: [AccountId: [ChainAssetId: BigUInt]] = [:] + private var crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]] = [:] + private var crowdloanContributionsMapping: [String: CrowdloanContributionId] = [:] + private var prices: [ChainAssetId: PriceData] = [:] + private var chains: [ChainModel.Id: ChainModel] = [:] + + func didReceiveBalancesChanges(_ changes: [DataProviderChange]) { + for change in changes { + switch change { + case let .insert(item), let .update(item): + var accountBalance = balances[item.accountId] ?? [:] + accountBalance[item.chainAssetId] = item.totalInPlank + balances[item.accountId] = accountBalance + + identifierMapping[item.identifier] = AssetBalanceId( + chainId: item.chainAssetId.chainId, + assetId: item.chainAssetId.assetId, + accountId: item.accountId + ) + case let .delete(deletedIdentifier): + if let accountBalanceId = identifierMapping[deletedIdentifier] { + var accountBalance = balances[accountBalanceId.accountId] + accountBalance?[accountBalanceId.chainAssetId] = nil + balances[accountBalanceId.accountId] = accountBalance + } + + identifierMapping[deletedIdentifier] = nil + } + } + } + + func didReceiveChainChanges(_ changes: [DataProviderChange]) { + chains = changes.mergeToDict(chains) + } + + func didReceivePrice(_ changes: [ChainAssetId: DataProviderChange]) { + prices = changes.reduce(into: prices) { accum, keyValue in + accum[keyValue.key] = keyValue.value.item + } + } + + func didReceiveCrowdloanContributionChanges(_ changes: [DataProviderChange]) { + for change in changes { + switch change { + case let .insert(item), let .update(item): + let previousAmount = crowdloanContributionsMapping[item.identifier]?.amount ?? 0 + var accountCrowdloan = crowdloanContributions[item.accountId] ?? [:] + let value: BigUInt = accountCrowdloan[item.chainId] ?? 0 + accountCrowdloan[item.chainId] = value - previousAmount + item.amount + crowdloanContributions[item.accountId] = accountCrowdloan + crowdloanContributionsMapping[item.identifier] = CrowdloanContributionId( + chainId: item.chainId, + accountId: item.accountId, + amount: item.amount + ) + case let .delete(deletedIdentifier): + if let accountContributionId = crowdloanContributionsMapping[deletedIdentifier] { + var accountContributions = crowdloanContributions[accountContributionId.accountId] + if let contribution = accountContributions?[accountContributionId.chainId], + contribution > accountContributionId.amount { + let newAmount = contribution - accountContributionId.amount + accountContributions?[accountContributionId.chainId] = newAmount + } else { + accountContributions?[accountContributionId.chainId] = nil + } + crowdloanContributions[accountContributionId.accountId] = accountContributions + } + + crowdloanContributionsMapping[deletedIdentifier] = nil + } + } + } + + func calculateValue( + chains: [ChainModel.Id: ChainModel], + balances: [ChainAssetId: BigUInt], + prices: [ChainAssetId: PriceData], + includingChainIds: Set, + excludingChainIds: Set + ) -> Decimal { + balances.reduce(Decimal.zero) { amount, chainAssetBalance in + let includingChain = includingChainIds.isEmpty || includingChainIds.contains(chainAssetBalance.key.chainId) + let excludingChain = excludingChainIds.contains(chainAssetBalance.key.chainId) + + guard + includingChain, !excludingChain, + let priceData = prices[chainAssetBalance.key], + let price = Decimal(string: priceData.price), + let asset = chains[chainAssetBalance.key.chainId]?.asset(for: chainAssetBalance.key.assetId), + let decimalBalance = Decimal.fromSubstrateAmount( + chainAssetBalance.value, + precision: Int16(bitPattern: asset.precision) + ) else { + return amount + } + + return amount + decimalBalance * price + } + } + + private func calculateCrowdloanContribution( + _ contributions: [ChainModel.Id: BigUInt], + chains: [ChainModel.Id: ChainModel], + prices: [ChainAssetId: PriceData] + ) -> Decimal { + contributions.reduce(0) { result, contribution in + guard let asset = chains[contribution.key]?.utilityAsset(), + let priceData = prices[ChainAssetId(chainId: contribution.key, assetId: asset.assetId)], + let price = Decimal(string: priceData.price) else { + return result + } + guard let decimalAmount = Decimal.fromSubstrateAmount( + contribution.value, + precision: Int16(bitPattern: asset.precision) + ) else { + return result + } + + return result + decimalAmount * price + } + } + + func calculateTotalValue(for accountId: AccountId, excludingChainIds: Set) -> Decimal { + let balances = calculateValue( + chains: chains, + balances: balances[accountId] ?? [:], + prices: prices, + includingChainIds: Set(), + excludingChainIds: excludingChainIds + ) + + let contributions = crowdloanContributions[accountId]? + .filter { !chainAccountIds.contains($0.key) } ?? [:] + + let crowdloans = calculateCrowdloanContribution( + contributions, + chains: chains, + prices: prices + ) + + return balances + crowdloans + } +} + +extension BalancesCalculator: BalancesCalculating { + func calculateTotalValue(for wallet: MetaAccountModel) -> Decimal { + let chainAccountIds = wallet.chainAccounts.map(\.chainId) + + var totalValue: Decimal = 0.0 + + if let substrateAccountId = wallet.substrateAccountId { + totalValue += calculateTotalValue(for: substrateAccountId, excludingChainIds: chainAccountIds) + } + + if let ethereumAddress = wallet.ethereumAddress { + totalValue += calculateTotalValue(for: ethereumAddress, excludingChainIds: chainAccountIds) + } + + wallet.chainAccounts.forEach { chainAccount in + totalValue += calculateValue( + chains: chains, + balances: balances[chainAccount.accountId] ?? [:], + prices: prices, + includingChainIds: [chainAccount.chainId], + excludingChainIds: Set() + ) + let contributions = crowdloanContributions[chainAccount.accountId] ?? [:] + totalValue += calculateCrowdloanContribution( + contributions, + chains: chains, + prices: prices + ) + } + + return totalValue + } +} diff --git a/novawallet/Common/Helpers/BalancesStore.swift b/novawallet/Common/Helpers/BalancesStore.swift new file mode 100644 index 0000000000..ae201c6b31 --- /dev/null +++ b/novawallet/Common/Helpers/BalancesStore.swift @@ -0,0 +1,194 @@ +import Foundation +import RobinHood + +enum BalancesStoreError: Error { + case priceFailed(Error) + case balancesFailed(Error) + case crowdloansFailed(Error) +} + +protocol BalancesStoreDelegate: AnyObject { + func balancesStore(_ balancesStore: BalancesStoreProtocol, didUpdate calculator: BalancesCalculating) + func balancesStore(_ balancesStore: BalancesStoreProtocol, didReceive error: BalancesStoreError) +} + +protocol BalancesStoreProtocol: AnyObject { + var delegate: BalancesStoreDelegate? { get set } + + func setup() +} + +final class BalancesStore { + let chainRegistry: ChainRegistryProtocol + let walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol + let priceLocalSubscriptionFactory: PriceProviderFactoryProtocol + let crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactoryProtocol + + weak var delegate: BalancesStoreDelegate? + + private(set) var priceSubscription: StreamableProvider? + private(set) var assetsSubscription: StreamableProvider? + private(set) var walletsSubscription: StreamableProvider? + private(set) var crowdloansSubscription: StreamableProvider? + private(set) var availableTokenPrice: [ChainAssetId: AssetModel.PriceId] = [:] + + private var calculator: BalancesCalculator? + + init( + chainRegistry: ChainRegistryProtocol, + walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, + priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, + currencyManager: CurrencyManagerProtocol, + crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactoryProtocol + ) { + self.chainRegistry = chainRegistry + self.walletLocalSubscriptionFactory = walletLocalSubscriptionFactory + self.priceLocalSubscriptionFactory = priceLocalSubscriptionFactory + self.crowdloansLocalSubscriptionFactory = crowdloansLocalSubscriptionFactory + self.currencyManager = currencyManager + } + + private func subscribeAssets() { + assetsSubscription = subscribeAllBalancesProvider() + } + + private func subscribeToCrowdloans() { + crowdloansSubscription = subscribeToAllCrowdloansProvider() + } + + private func subscribeChains() { + chainRegistry.chainsSubscribe(self, runningInQueue: .main) { [weak self] changes in + self?.calculator?.didReceiveChainChanges(changes) + self?.handle(changes: changes) + } + } + + private func handle(changes: [DataProviderChange]) { + let prevPrices = availableTokenPrice + for change in changes { + switch change { + case let .insert(chain), let .update(chain): + availableTokenPrice = availableTokenPrice.filter { $0.key.chainId != chain.chainId } + + availableTokenPrice = chain.assets.reduce(into: availableTokenPrice) { result, asset in + guard let priceId = asset.priceId else { + return + } + + let chainAssetId = ChainAssetId(chainId: chain.chainId, assetId: asset.assetId) + result[chainAssetId] = priceId + } + case let .delete(deletedIdentifier): + availableTokenPrice = availableTokenPrice.filter { $0.key.chainId != deletedIdentifier } + } + } + + if prevPrices != availableTokenPrice { + updatePriceProvider( + for: Set(availableTokenPrice.values), + currency: selectedCurrency + ) + } + } + + private func updatePriceProvider( + for priceIdSet: Set, + currency: Currency + ) { + priceSubscription = nil + + let priceIds = Array(priceIdSet).sorted() + + guard !priceIds.isEmpty else { + return + } + + priceSubscription = priceLocalSubscriptionFactory.getAllPricesStreamableProvider( + for: priceIds, + currency: currency + ) + + let updateClosure = { [weak self] (changes: [DataProviderChange]) in + guard let strongSelf = self else { + return + } + + let mappedChanges = changes.reduce( + using: .init(), + availableTokenPrice: strongSelf.availableTokenPrice, + currency: currency + ) + + strongSelf.calculator?.didReceivePrice(mappedChanges) + + return + } + + let failureClosure = { [weak self] (error: Error) in + guard let strongSelf = self else { + return + } + + strongSelf.delegate?.balancesStore(strongSelf, didReceive: .priceFailed(error)) + } + + let options = StreamableProviderObserverOptions( + alwaysNotifyOnRefresh: true, + waitsInProgressSyncOnAdd: false, + initialSize: 0, + refreshWhenEmpty: false + ) + + priceSubscription?.addObserver( + self, + deliverOn: .main, + executing: updateClosure, + failing: failureClosure, + options: options + ) + + priceSubscription?.refresh() + } +} + +extension BalancesStore: BalancesStoreProtocol { + func setup() { + calculator = BalancesCalculator() + + subscribeChains() + subscribeAssets() + subscribeToCrowdloans() + } +} + +extension BalancesStore: WalletLocalStorageSubscriber, WalletLocalSubscriptionHandler { + func handleAllBalances(result: Result<[DataProviderChange], Error>) { + switch result { + case let .success(changes): + calculator?.didReceiveBalancesChanges(changes) + case let .failure(error): + delegate?.balancesStore(self, didReceive: .balancesFailed(error)) + } + } +} + +extension BalancesStore: CrowdloanContributionLocalSubscriptionHandler, CrowdloansLocalStorageSubscriber { + func handleAllCrowdloans(result: Result<[DataProviderChange], Error>) { + switch result { + case let .success(changes): + calculator?.didReceiveCrowdloanContributionChanges(changes) + case let .failure(error): + delegate?.balancesStore(self, didReceive: .crowdloansFailed(error)) + } + } +} + +extension BalancesStore: SelectedCurrencyDepending { + func applyCurrency() { + guard delegate != nil else { + return + } + + updatePriceProvider(for: Set(availableTokenPrice.values), currency: selectedCurrency) + } +} diff --git a/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift b/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift index c2e84ace92..2dabcbf680 100644 --- a/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift +++ b/novawallet/Common/View/ScrollableContainerView/ScrollableContainerLayoutView.swift @@ -41,14 +41,50 @@ class ScrollableContainerLayoutView: UIView { stackView.setCustomSpacing(value, after: view) } } + + func insertArrangedSubview(_ view: UIView, after oldView: UIView, spacingAfter value: CGFloat = 0) { + stackView.insertArranged(view: view, after: oldView) + + if value > 0 { + stackView.setCustomSpacing(value, after: view) + } + } + + func applyWarning( + on warningView: inout InlineAlertView?, + after view: UIView?, + text: String?, + spacing: CGFloat = 0 + ) { + if let text = text { + if warningView == nil { + let newView = InlineAlertView.warning() + + if let afterView = view { + insertArrangedSubview(newView, after: afterView, spacingAfter: spacing) + } else { + addArrangedSubview(newView, spacingAfter: spacing) + } + + warningView = newView + } + + warningView?.contentView.detailsLabel.text = text + } else { + warningView?.removeFromSuperview() + warningView = nil + } + + setNeedsLayout() + } } -class ScrollableContainerActionLayoutView: ScrollableContainerLayoutView { - let actionLoadableView = LoadableActionView() +class SCGenericActionLayoutView: ScrollableContainerLayoutView { + let genericActionView = A() override func setupLayout() { - addSubview(actionLoadableView) - actionLoadableView.snp.makeConstraints { make in + addSubview(genericActionView) + genericActionView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) make.bottom.equalTo(safeAreaLayoutGuide).inset(UIConstants.actionBottomInset) make.height.equalTo(UIConstants.actionHeight) @@ -57,7 +93,11 @@ class ScrollableContainerActionLayoutView: ScrollableContainerLayoutView { addSubview(containerView) containerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() - make.bottom.equalTo(actionLoadableView.snp.top).offset(-8.0) + make.bottom.equalTo(genericActionView.snp.top).offset(-8.0) } } } + +typealias SCLoadableActionLayoutView = SCGenericActionLayoutView + +typealias SCSingleActionLayoutView = SCGenericActionLayoutView diff --git a/novawallet/Common/View/StackTable/StackWalletAmountCell.swift b/novawallet/Common/View/StackTable/StackWalletAmountCell.swift new file mode 100644 index 0000000000..32e6771619 --- /dev/null +++ b/novawallet/Common/View/StackTable/StackWalletAmountCell.swift @@ -0,0 +1,26 @@ +import UIKit + +final class StackWalletAmountCell: RowView>, + StackTableViewCellProtocol { + var walletView: WalletTotalAmountView { rowContentView.titleView } + var disclosureIndicatorView: UIImageView { rowContentView.valueView } + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + } + + convenience init() { + self.init(frame: CGRect(origin: .zero, size: CGSize(width: 340, height: 44.0))) + } + + private func setupStyle() { + let icon = R.image.iconSmallArrow()?.tinted(with: R.color.colorTextSecondary()!) + disclosureIndicatorView.image = icon + } + + func bind(viewModel: WalletTotalAmountView.ViewModel) { + walletView.bind(viewModel: viewModel) + } +} diff --git a/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift b/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift new file mode 100644 index 0000000000..e739139189 --- /dev/null +++ b/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift @@ -0,0 +1,50 @@ +import UIKit + +final class WalletTotalAmountView: IconDetailsGenericView { + var titleLabel: UILabel { detailsView.valueTop } + var subtitleLabel: UILabel { detailsView.valueBottom } + + var imageViewModel: ImageViewModelProtocol? + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + } + + func setupStyle() { + iconWidth = 32 + spacing = 12 + detailsView.spacing = 0 + + titleLabel.apply(style: .regularSubhedlinePrimary) + subtitleLabel.apply(style: .footnoteSecondary) + } +} + +extension WalletTotalAmountView { + struct ViewModel { + let icon: ImageViewModelProtocol? + let name: String + let amount: String + } + + func cancelImageLoading() { + imageViewModel?.cancel(on: imageView) + imageView.image = nil + } + + func bind(viewModel: ViewModel) { + cancelImageLoading() + + imageViewModel = viewModel.icon + imageViewModel?.loadImage( + on: imageView, + targetSize: CGSize(width: iconWidth, height: iconWidth), + animated: true + ) + + titleLabel.text = viewModel.name + subtitleLabel.text = viewModel.amount + } +} diff --git a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift index acac5afa43..a6fe5cc7c0 100644 --- a/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift +++ b/novawallet/Modules/DApp/DAppAuthConfirm/Model/DAppAuthRequest.swift @@ -8,9 +8,8 @@ struct DAppAuthRequest { let dApp: String let dAppIcon: URL? - let requiredChains: Set - let optionalChains: Set? - let unknownChains: Set? + let requiredChains: DAppChainsResolution + let optionalChains: DAppChainsResolution? } struct DAppAuthResponse { diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift index c93adc78fe..7c9ff1dc78 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/MetamaskStates/DAppMetamaskWaitingAuthState.swift @@ -52,9 +52,8 @@ final class DAppMetamaskWaitingAuthState: DAppMetamaskBaseState { origin: host, dApp: host, dAppIcon: nil, - requiredChains: Set(), - optionalChains: nil, - unknownChains: nil + requiredChains: .init(), + optionalChains: nil ) let nextState = DAppMetamaskAuthorizingState( diff --git a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift index 3d5b8938d2..3d79999b80 100644 --- a/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift +++ b/novawallet/Modules/DApp/DAppBrowser/StateMachine/PolkadotExtensionStates/DAppBrowserWaitingAuthState.swift @@ -48,9 +48,8 @@ final class DAppBrowserWaitingAuthState: DAppBrowserBaseState { origin: message.request?.origin?.stringValue, dApp: message.url ?? "", dAppIcon: dataSource.dApp?.icon, - requiredChains: [], - optionalChains: nil, - unknownChains: nil + requiredChains: .init(), + optionalChains: nil ) let nextState = DAppBrowserAuthorizingState( diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift new file mode 100644 index 0000000000..60254d24a3 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift @@ -0,0 +1,9 @@ +import UIKit + +final class DAppWalletAuthInteractor { + weak var presenter: DAppWalletAuthInteractorOutputProtocol? +} + +extension DAppWalletAuthInteractor: DAppWalletAuthInteractorInputProtocol { + func fetchTotalValue(for _: MetaAccountModel) {} +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift new file mode 100644 index 0000000000..7c0ae4df62 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift @@ -0,0 +1,91 @@ +import Foundation +import SoraFoundation + +final class DAppWalletAuthPresenter { + weak var view: DAppWalletAuthViewProtocol? + let wireframe: DAppWalletAuthWireframeProtocol + let interactor: DAppWalletAuthInteractorInputProtocol + let viewModelFactory: DAppWalletAuthViewModelFactoryProtocol + let logger: LoggerProtocol + + private var selectedWallet: MetaAccountModel + private var totalWalletValue: Decimal? + private var request: DAppAuthRequest + + weak var delegate: DAppAuthDelegate? + + init( + request: DAppAuthRequest, + delegate: DAppAuthDelegate, + viewModelFactory: DAppWalletAuthViewModelFactoryProtocol, + interactor: DAppWalletAuthInteractorInputProtocol, + wireframe: DAppWalletAuthWireframeProtocol, + localizationManager: LocalizationManagerProtocol, + logger: LoggerProtocol + ) { + self.request = request + selectedWallet = request.wallet + self.delegate = delegate + self.viewModelFactory = viewModelFactory + self.interactor = interactor + self.wireframe = wireframe + self.logger = logger + self.localizationManager = localizationManager + } + + private func complete(with result: Bool) { + let response = DAppAuthResponse(approved: result, wallet: selectedWallet) + delegate?.didReceiveAuthResponse(response, for: request) + wireframe.close(from: view) + } + + private func updateView() { + guard + let viewModel = viewModelFactory.createViewModel( + from: request, + wallet: selectedWallet, + totalWalletValue: totalWalletValue, + locale: selectedLocale + ) else { + return + } + + view?.didReceive(viewModel: viewModel) + } +} + +extension DAppWalletAuthPresenter: DAppWalletAuthPresenterProtocol { + func setup() { + updateView() + + interactor.fetchTotalValue(for: selectedWallet) + } + + func approve() { + complete(with: true) + } + + func reject() { + complete(with: false) + } +} + +extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { + func didFetchTotalValue(_ value: Decimal, wallet: MetaAccountModel) { + guard wallet.metaId == selectedWallet.metaId else { + return + } + + totalWalletValue = value + + updateView() + } +} + +extension DAppWalletAuthPresenter: Localizable { + func applyLocalization() { + if let view = view, view.isSetup { + updateView() + } + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift new file mode 100644 index 0000000000..6805d50672 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift @@ -0,0 +1,21 @@ +protocol DAppWalletAuthViewProtocol: ControllerBackedProtocol { + func didReceive(viewModel: DAppWalletAuthViewModel) +} + +protocol DAppWalletAuthPresenterProtocol: AnyObject { + func setup() + func approve() + func reject() +} + +protocol DAppWalletAuthInteractorInputProtocol: AnyObject { + func fetchTotalValue(for wallet: MetaAccountModel) +} + +protocol DAppWalletAuthInteractorOutputProtocol: AnyObject { + func didFetchTotalValue(_ value: Decimal, wallet: MetaAccountModel) +} + +protocol DAppWalletAuthWireframeProtocol: AnyObject { + func close(from view: DAppWalletAuthViewProtocol?) +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift new file mode 100644 index 0000000000..4243cd62f9 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift @@ -0,0 +1,125 @@ +import UIKit +import SoraFoundation + +final class DAppWalletAuthViewController: UIViewController, ViewHolder { + typealias RootViewType = DAppWalletAuthViewLayout + + let presenter: DAppWalletAuthPresenterProtocol + let localizableTitle: LocalizableResource + + init( + title: LocalizableResource, + presenter: DAppWalletAuthPresenterProtocol, + localizationManager: LocalizationManagerProtocol + ) { + localizableTitle = title + self.presenter = presenter + + super.init(nibName: nil, bundle: nil) + + self.localizationManager = localizationManager + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = DAppWalletAuthViewLayout() + } + + override func viewDidLoad() { + super.viewDidLoad() + + presenter.setup() + } + + private func setupLocalization() { + let languages = selectedLocale.rLanguages + + title = localizableTitle.value(for: selectedLocale) + rootView.subtitleLabel.text = R.string.localizable.dappAuthSubtitle(preferredLanguages: languages) + rootView.dappCell.titleLabel.text = R.string.localizable.commonDapp(preferredLanguages: languages) + } + + private func setupButtonsLocalization() { + rootView.approveButton?.imageWithTitleView?.title = R.string.localizable.commonAllow( + preferredLanguages: selectedLocale.rLanguages + ) + + rootView.rejectButton?.imageWithTitleView?.title = R.string.localizable.commonReject( + preferredLanguages: selectedLocale.rLanguages + ) + } + + private func setupButtonsHandlers() { + rootView.approveButton?.addTarget( + self, + action: #selector(actionApprove), + for: .touchUpInside + ) + + rootView.rejectButton?.addTarget( + self, + action: #selector(actionReject), + for: .touchUpInside + ) + } + + @objc func actionApprove() { + presenter.approve() + } + + @objc func actionReject() { + presenter.reject() + } +} + +extension DAppWalletAuthViewController: DAppWalletAuthViewProtocol { + func didReceive(viewModel: DAppWalletAuthViewModel) { + rootView.sourceAppIconView.bind( + viewModel: viewModel.sourceImageViewModel, + size: DAppIconLargeConstants.displaySize + ) + + rootView.destinationAppIconView.bind( + viewModel: viewModel.destinationImageViewModel, + size: DAppIconLargeConstants.displaySize + ) + + rootView.titleLabel.text = R.string.localizable.dappAuthTitle( + viewModel.dAppName, + preferredLanguages: selectedLocale.rLanguages + ) + + rootView.dappCell.bind(details: viewModel.dAppHost) + + rootView.networksCell.bindNetworks(viewModel: viewModel.networks, locale: selectedLocale) + + rootView.applyNetworksWarning(text: viewModel.networksWarning) + + rootView.walletCell.bind(viewModel: viewModel.wallet) + + rootView.applyWalletWarning(text: viewModel.walletWarning) + + rootView.setupRejectButton() + + if viewModel.canApprove { + rootView.setupApproveButton() + } else { + rootView.removeApproveButton() + } + + setupButtonsLocalization() + setupButtonsHandlers() + } +} + +extension DAppWalletAuthViewController: Localizable { + func applyLocalization() { + if isViewLoaded { + setupLocalization() + } + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift new file mode 100644 index 0000000000..f6b665b48d --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift @@ -0,0 +1,60 @@ +import Foundation +import SoraFoundation + +struct DAppWalletAuthViewFactory { + static func createWalletConnectView( + for request: DAppAuthRequest, + delegate: DAppAuthDelegate + ) -> DAppWalletAuthViewProtocol? { + let title = LocalizableResource { locale in + R.string.localizable.commonWalletConnect(preferredLanguages: locale.rLanguages) + } + + return createView(for: request, delegate: delegate, title: title) + } + + static func createView( + for request: DAppAuthRequest, + delegate: DAppAuthDelegate, + title: LocalizableResource + ) -> DAppWalletAuthViewProtocol? { + guard let currencyManager = CurrencyManager.shared else { + return nil + } + + let interactor = DAppWalletAuthInteractor() + let wireframe = DAppWalletAuthWireframe() + + let localizationManager = LocalizationManager.shared + + let priceAssetInfoFactory = PriceAssetInfoFactory(currencyManager: currencyManager) + + let fiatViewModelFactory = BalanceViewModelFactory( + targetAssetInfo: priceAssetInfoFactory.createAssetBalanceDisplayInfo(from: nil), + priceAssetInfoFactory: priceAssetInfoFactory + ) + + let viewModelFactory = DAppWalletAuthViewModelFactory(fiatBalanceInfoFactory: fiatViewModelFactory) + + let presenter = DAppWalletAuthPresenter( + request: request, + delegate: delegate, + viewModelFactory: viewModelFactory, + interactor: interactor, + wireframe: wireframe, + localizationManager: localizationManager, + logger: Logger.shared + ) + + let view = DAppWalletAuthViewController( + title: title, + presenter: presenter, + localizationManager: localizationManager + ) + + presenter.view = view + interactor.presenter = presenter + + return view + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift new file mode 100644 index 0000000000..56de697291 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift @@ -0,0 +1,138 @@ +import UIKit + +final class DAppWalletAuthViewLayout: SCGenericActionLayoutView { + let sourceAppIconView: DAppIconView = .create { view in + view.contentInsets = DAppIconLargeConstants.insets + } + + let destinationAppIconView: DAppIconView = .create { view in + view.contentInsets = DAppIconLargeConstants.insets + } + + let accessImageView: UIImageView = .create { view in + view.image = R.image.iconDappAccess()! + } + + let titleLabel: UILabel = .create { view in + view.apply(style: .title3Primary) + view.textAlignment = .center + view.numberOfLines = 3 + } + + let subtitleLabel: UILabel = .create { view in + view.apply(style: .footnoteSecondary) + view.textAlignment = .center + view.numberOfLines = 2 + } + + let dappTableView = StackTableView() + let dappCell = StackTableCell() + let networksCell = StackInfoTableCell() + + let walletTableView = StackTableView() + let walletCell = StackWalletAmountCell() + + private(set) var rejectButton: TriangularedButton? + private(set) var approveButton: TriangularedButton? + + private(set) var networksWarningView: InlineAlertView? + private(set) var walletWarningView: InlineAlertView? + + func setupRejectButton() { + guard rejectButton == nil else { + return + } + + let button = TriangularedButton() + button.applySecondaryDefaultStyle() + genericActionView.insertArrangedSubview(button, at: 0) + + rejectButton = button + } + + func setupApproveButton() { + guard approveButton == nil else { + return + } + + let button = TriangularedButton() + button.applyEnabledStyle() + genericActionView.addArrangedSubview(button) + + approveButton = button + } + + func removeApproveButton() { + approveButton?.removeFromSuperview() + approveButton = nil + } + + func applyNetworksWarning(text: String?) { + applyWarning( + on: &networksWarningView, + after: dappTableView, + text: text, + spacing: 8.0 + ) + } + + func applyWalletWarning(text: String?) { + applyWarning( + on: &walletWarningView, + after: walletTableView, + text: text, + spacing: 8.0 + ) + } + + override func setupLayout() { + super.setupLayout() + + genericActionView.axis = .horizontal + genericActionView.spacing = 16 + + let headerView = UIView() + + headerView.addSubview(accessImageView) + accessImageView.snp.makeConstraints { make in + make.top.equalToSuperview().inset(36.0) + make.centerX.equalToSuperview() + } + + headerView.addSubview(sourceAppIconView) + sourceAppIconView.snp.makeConstraints { make in + make.centerY.equalTo(accessImageView.snp.centerY) + make.trailing.equalTo(accessImageView.snp.leading).offset(-8.0) + make.size.equalTo(DAppIconLargeConstants.size) + } + + headerView.addSubview(destinationAppIconView) + destinationAppIconView.snp.makeConstraints { make in + make.centerY.equalTo(accessImageView.snp.centerY) + make.leading.equalTo(accessImageView.snp.trailing).offset(8.0) + make.size.equalTo(DAppIconLargeConstants.size) + } + + headerView.addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16.0) + make.top.equalToSuperview().inset(112.0) + } + + headerView.addSubview(subtitleLabel) + subtitleLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16.0) + make.top.equalTo(titleLabel.snp.bottom).offset(12.0) + make.bottom.equalToSuperview() + } + + addArrangedSubview(headerView, spacingAfter: 24) + + addArrangedSubview(dappTableView, spacingAfter: 8) + dappTableView.addArrangedSubview(dappCell) + dappTableView.addArrangedSubview(networksCell) + + addArrangedSubview(walletTableView, spacingAfter: 8) + walletTableView.addArrangedSubview(walletCell) + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift new file mode 100644 index 0000000000..3f07d77ba9 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift @@ -0,0 +1,7 @@ +import Foundation + +final class DAppWalletAuthWireframe: DAppWalletAuthWireframeProtocol { + func close(from view: DAppWalletAuthViewProtocol?) { + view?.controller.presentingViewController?.dismiss(animated: true, completion: nil) + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift new file mode 100644 index 0000000000..86e62e349f --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift @@ -0,0 +1,16 @@ +import Foundation + +struct DAppWalletAuthViewModel { + let sourceImageViewModel: ImageViewModelProtocol? + let destinationImageViewModel: ImageViewModelProtocol? + let dAppName: String + let dAppHost: String + let networks: DAppNetworksViewModel + let networksWarning: String? + let wallet: WalletTotalAmountView.ViewModel + let walletWarning: String? + + var canApprove: Bool { + networksWarning != nil || walletWarning != nil + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift new file mode 100644 index 0000000000..16671f9606 --- /dev/null +++ b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift @@ -0,0 +1,135 @@ +import UIKit + +protocol DAppWalletAuthViewModelFactoryProtocol { + func createViewModel( + from request: DAppAuthRequest, + wallet: MetaAccountModel, + totalWalletValue: Decimal?, + locale: Locale + ) -> DAppWalletAuthViewModel? +} + +final class DAppWalletAuthViewModelFactory { + let walletViewModelFactory = WalletAccountViewModelFactory() + let networksViewModelFactory = DAppNetworksViewModelFactory() + + let fiatBalanceInfoFactory: BalanceViewModelFactoryProtocol + + init(fiatBalanceInfoFactory: BalanceViewModelFactoryProtocol) { + self.fiatBalanceInfoFactory = fiatBalanceInfoFactory + } + + private func createImageViewModel(from staticImage: UIImage) -> ImageViewModelProtocol { + StaticImageViewModel(image: staticImage) + } + + private func createDAppImageViewModel(from url: URL?) -> ImageViewModelProtocol { + if let url = url { + return RemoteImageViewModel(url: url) + } else { + return createImageViewModel(from: R.image.iconDefaultDapp()!) + } + } + + private func createWalletViewModel( + from wallet: MetaAccountModel, + totalValue: Decimal?, + locale: Locale + ) throws -> WalletTotalAmountView.ViewModel { + let viewModel = try walletViewModelFactory.createDisplayViewModel(from: wallet) + + let totalValueString = totalValue.flatMap { fiatBalanceInfoFactory.amountFromValue($0) } + + return .init( + icon: viewModel.imageViewModel, + name: viewModel.name, + amount: totalValueString?.value(for: locale) ?? "" + ) + } + + private func detectNetworksWarning( + from request: DAppAuthRequest, + locale: Locale + ) -> String? { + guard !request.requiredChains.hasUnresolved else { + return nil + } + + return R.string.localizable.dappsMissingRequiredNetworksWarningFormat( + request.dApp, + preferredLanguages: locale.rLanguages + ) + } + + private func detectWalletWarning( + from request: DAppAuthRequest, + wallet: MetaAccountModel, + locale: Locale + ) -> String? { + let noAccountChains = request.requiredChains.resolved.filter { chain in + wallet.fetch(for: chain.accountRequest()) == nil + } + + guard !noAccountChains.isEmpty else { + return nil + } + + let chains = noAccountChains.sorted { chain1, chain2 in + ChainModelCompator.defaultComparator(chain1: chain1, chain2: chain2) + } + + let name = networksViewModelFactory.createChainNamesString( + from: chains, + maxCount: 3, + locale: locale + ) + + return R.string.localizable.missingAccountsWarningFormat( + format: chains.count, + format: name, + preferredLanguages: locale.rLanguages + ) + } +} + +extension DAppWalletAuthViewModelFactory: DAppWalletAuthViewModelFactoryProtocol { + func createViewModel( + from request: DAppAuthRequest, + wallet: MetaAccountModel, + totalWalletValue: Decimal?, + locale: Locale + ) -> DAppWalletAuthViewModel? { + guard let walletViewModel = try? createWalletViewModel( + from: wallet, + totalValue: totalWalletValue, + locale: locale + ) else { + return nil + } + + let sourceViewModel = createImageViewModel(from: R.image.iconDappExtension()!) + let destinationViewModel = createDAppImageViewModel(from: request.dAppIcon) + + let resolution = request.optionalChains?.merging(with: request.requiredChains) ?? + request.requiredChains + let networksViewModel = networksViewModelFactory.createViewModel(from: resolution) + let networksWarning = detectNetworksWarning(from: request, locale: locale) + + let walletWarning = detectWalletWarning( + from: request, + wallet: wallet, + locale: locale + ) + + return .init( + sourceImageViewModel: sourceViewModel, + destinationImageViewModel: destinationViewModel, + dAppName: request.dApp, + dAppHost: request.origin ?? "", + networks: networksViewModel, + networksWarning: networksWarning, + wallet: walletViewModel, + walletWarning: walletWarning + ) + } +} diff --git a/novawallet/Modules/DApp/Model/DAppChainsResolution.swift b/novawallet/Modules/DApp/Model/DAppChainsResolution.swift new file mode 100644 index 0000000000..dd3a8c20fb --- /dev/null +++ b/novawallet/Modules/DApp/Model/DAppChainsResolution.swift @@ -0,0 +1,28 @@ +import Foundation + +struct DAppChainsResolution { + let resolved: Set + let unresolved: Set + + var hasResolved: Bool { !resolved.isEmpty } + var hasUnresolved: Bool { !unresolved.isEmpty } + + var hasChains: Bool { hasResolved || hasUnresolved } + + init(resolved: Set = [], unresolved: Set = []) { + self.resolved = resolved + self.unresolved = unresolved + } + + init(wcResolution: WalletConnectChainsResolution) { + resolved = Set(wcResolution.resolved.values) + unresolved = wcResolution.unresolved + } + + func merging(with resolution: DAppChainsResolution) -> DAppChainsResolution { + let newResolved = resolved.union(resolution.resolved) + let newUnresolved = unresolved.union(resolution.unresolved) + + return .init(resolved: newResolved, unresolved: newUnresolved) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift b/novawallet/Modules/DApp/View/DAppNetworksViewModel.swift similarity index 81% rename from novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift rename to novawallet/Modules/DApp/View/DAppNetworksViewModel.swift index 01bee5296b..07e40161a2 100644 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModel.swift +++ b/novawallet/Modules/DApp/View/DAppNetworksViewModel.swift @@ -1,6 +1,6 @@ import Foundation -struct WalletConnectNetworksViewModel { +struct DAppNetworksViewModel { let network: NetworkViewModel? let supported: Int let unsupported: Int diff --git a/novawallet/Modules/DApp/View/DAppNetworksViewModelFactory.swift b/novawallet/Modules/DApp/View/DAppNetworksViewModelFactory.swift new file mode 100644 index 0000000000..865f6b3927 --- /dev/null +++ b/novawallet/Modules/DApp/View/DAppNetworksViewModelFactory.swift @@ -0,0 +1,36 @@ +import Foundation + +final class DAppNetworksViewModelFactory { + private lazy var networkViewModelFactory = NetworkViewModelFactory() + + func createViewModel(from networks: DAppChainsResolution) -> DAppNetworksViewModel { + let optNetwork = networks.resolved.min { chain1, chain2 in + ChainModelCompator.defaultComparator(chain1: chain1, chain2: chain2) + } + + let networkViewModel = optNetwork.map { networkViewModelFactory.createViewModel(from: $0) } + + return .init( + network: networkViewModel, + supported: networks.resolved.count, + unsupported: networks.unresolved.count + ) + } + + func createChainNamesString(from chains: [ChainModel], maxCount: Int, locale: Locale) -> String { + let namedChains = chains.prefix(maxCount) + let remainedCount = max(chains.count, maxCount) - maxCount + + let name = namedChains.map(\.name).joined(with: .commaSpace) + + if remainedCount > 0 { + return R.string.localizable.commonMoreFormat( + name, + "\(remainedCount)", + preferredLanguages: locale.rLanguages + ) + } else { + return name + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift index 90103f8575..16349419c5 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModel.swift @@ -10,6 +10,6 @@ struct WalletConnectSessionViewModel { let title: String let wallet: DisplayWalletViewModel? let host: String - let networks: WalletConnectNetworksViewModel + let networks: DAppNetworksViewModel let status: Status } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift index f9f23e4b72..0da821a509 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/ViewModel/WalletConnectSessionViewModelFactory.swift @@ -6,7 +6,7 @@ protocol WalletConnectSessionViewModelFactoryProtocol { final class WalletConnectSessionViewModelFactory: WalletConnectSessionViewModelFactoryProtocol { let walletViewModelFactory = WalletAccountViewModelFactory() - let networksViewModelFactory = WalletConnectNetworksViewModelFactory() + let networksViewModelFactory = DAppNetworksViewModelFactory() func createViewModel(from model: WalletConnectSession) -> WalletConnectSessionViewModel { let walletViewModel: DisplayWalletViewModel? = model.wallet.flatMap { wallet in @@ -22,7 +22,8 @@ final class WalletConnectSessionViewModelFactory: WalletConnectSessionViewModelF iconViewModel = StaticImageViewModel(image: icon) } - let networks = networksViewModelFactory.createViewModel(from: model.networks) + let resolution = DAppChainsResolution(wcResolution: model.networks) + let networks = networksViewModelFactory.createViewModel(from: resolution) return .init( iconViewModel: iconViewModel, diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift index 451bb5bb7f..05e3a7dd0c 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsViewLayout.swift @@ -1,6 +1,6 @@ import UIKit -final class WalletConnectSessionDetailsViewLayout: ScrollableContainerActionLayoutView { +final class WalletConnectSessionDetailsViewLayout: SCLoadableActionLayoutView { let titleView: GenericPairValueView = .create { view in view.setVerticalAndSpacing(24) view.stackView.alignment = .center @@ -19,6 +19,8 @@ final class WalletConnectSessionDetailsViewLayout: ScrollableContainerActionLayo let networksCell = StackInfoTableCell() let statusCell = StackStatusCell() + var actionLoadableView: LoadableActionView { genericActionView } + override func setupStyle() { super.setupStyle() diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index 055d693ded..45f5d313ce 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -41,12 +41,6 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { chainsStore: dataSource.chainsStore ) - let requiredChains = Set(resolution.requiredNamespaces.resolved.values) - let optionalChains = resolution.optionalNamespaces.map { Set($0.resolved.values) } - let unresolvedChains = resolution.requiredNamespaces.unresolved.union( - resolution.optionalNamespaces?.unresolved ?? [] - ) - let authRequest = DAppAuthRequest( transportName: DAppTransports.walletConnect, identifier: proposal.pairingTopic, @@ -54,9 +48,8 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { origin: proposal.proposer.url, dApp: proposal.proposer.name, dAppIcon: proposal.proposer.icons.first.flatMap { URL(string: $0) }, - requiredChains: requiredChains, - optionalChains: optionalChains, - unknownChains: !unresolvedChains.isEmpty ? unresolvedChains : nil + requiredChains: .init(wcResolution: resolution.requiredNamespaces), + optionalChains: resolution.optionalNamespaces.map { DAppChainsResolution(wcResolution: $0) } ) let nextState = WalletConnectStateAuthorizing( diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift index 29e6c9fd03..9fec736d36 100644 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift +++ b/novawallet/Modules/DApp/WalletConnect/ViewModel/StackInfoTableCell+WallectConnect.swift @@ -1,7 +1,7 @@ import Foundation extension StackInfoTableCell { - func bindNetworks(viewModel: WalletConnectNetworksViewModel, locale: Locale) { + func bindNetworks(viewModel: DAppNetworksViewModel, locale: Locale) { titleLabel.text = R.string.localizable.commonNetworksTitle( viewModel.totalNetworks, preferredLanguages: locale.rLanguages diff --git a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift deleted file mode 100644 index 84fe7d6b48..0000000000 --- a/novawallet/Modules/DApp/WalletConnect/ViewModel/WalletConnectNetworksViewModelFactory.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -final class WalletConnectNetworksViewModelFactory { - private lazy var networkViewModelFactory = NetworkViewModelFactory() - - func createViewModel(from networks: WalletConnectChainsResolution) -> WalletConnectNetworksViewModel { - let optNetwork = networks.resolved.values.min { - ChainModelCompator.defaultComparator(chain1: $0, chain2: $1) - } - - let networkViewModel = optNetwork.map { networkViewModelFactory.createViewModel(from: $0) } - - return .init( - network: networkViewModel, - supported: networks.resolved.count, - unsupported: networks.unresolved.count - ) - } -} diff --git a/novawallet/Modules/WalletsList/Common/WalletsListTableViewCell.swift b/novawallet/Modules/WalletsList/Common/WalletsListTableViewCell.swift index 5279aa3912..3438cda5c7 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListTableViewCell.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListTableViewCell.swift @@ -1,74 +1,27 @@ -import Foundation import UIKit -class WalletsListTableViewCell: UITableViewCell { - private enum Constants { - static let iconSize: CGFloat = 32.0 - } - - let iconImageView = UIImageView() - - let infoView: MultiValueView = { - let view = MultiValueView() - view.valueTop.textColor = R.color.colorTextPrimary() - view.valueTop.font = .regularSubheadline - view.valueTop.textAlignment = .left - view.valueBottom.textColor = R.color.colorTextSecondary() - view.valueBottom.font = .regularFootnote - view.valueBottom.textAlignment = .left - view.spacing = 2.0 - return view - }() +protocol WalletsListTableViewCellProtocol { + func bind(viewModel: WalletsListViewModel) +} - private var viewModel: WalletsListViewModel? +class WalletsListTableViewCell: PlainBaseTableViewCell< + GenericTitleValueView +>, WalletsListTableViewCellProtocol { + var infoView: WalletTotalAmountView { contentDisplayView.titleView } override func prepareForReuse() { super.prepareForReuse() - viewModel?.icon?.cancel(on: iconImageView) + infoView.cancelImageLoading() } - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + override func setupStyle() { + super.setupStyle() backgroundColor = .clear - - let backgroundView = UIView() - backgroundView.backgroundColor = R.color.colorCellBackgroundPressed() - selectedBackgroundView = backgroundView - - setupLayout() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") } func bind(viewModel: WalletsListViewModel) { - self.viewModel?.icon?.cancel(on: iconImageView) - - self.viewModel = viewModel - - let targetSize = CGSize(width: Constants.iconSize, height: Constants.iconSize) - viewModel.icon?.loadImage(on: iconImageView, targetSize: targetSize, animated: true) - - infoView.valueTop.text = viewModel.name - infoView.valueBottom.text = viewModel.value ?? "" - } - - func setupLayout() { - contentView.addSubview(iconImageView) - iconImageView.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(16.0) - make.centerY.equalToSuperview() - make.size.equalTo(Constants.iconSize) - } - - contentView.addSubview(infoView) - infoView.snp.makeConstraints { make in - make.leading.equalTo(iconImageView.snp.trailing).offset(12.0) - make.centerY.equalToSuperview() - } + infoView.bind(viewModel: viewModel.walletAmountViewModel) } } diff --git a/novawallet/Modules/WalletsList/Common/WalletsListViewController.swift b/novawallet/Modules/WalletsList/Common/WalletsListViewController.swift index 9e423eafad..0f8db4e6f6 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListViewController.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListViewController.swift @@ -1,8 +1,9 @@ import UIKit import SoraFoundation -class WalletsListViewController: UIViewController, - ViewHolder, UITableViewDataSource, UITableViewDelegate { +class WalletsListViewController< + Layout: WalletsListViewLayout, Cell: WalletsListTableViewCellProtocol & UITableViewCell +>: UIViewController, ViewHolder, UITableViewDataSource, UITableViewDelegate { typealias RootViewType = Layout let basePresenter: WalletsListPresenterProtocol diff --git a/novawallet/Modules/WalletsList/Common/WalletsListViewModel.swift b/novawallet/Modules/WalletsList/Common/WalletsListViewModel.swift index 59792a31ce..e6ee1b1398 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListViewModel.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListViewModel.swift @@ -3,9 +3,7 @@ import SubstrateSdk struct WalletsListViewModel { let identifier: String - let name: String - let icon: ImageViewModelProtocol? - let value: String? + let walletAmountViewModel: WalletTotalAmountView.ViewModel let isSelected: Bool } diff --git a/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift b/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift index 31fbef399f..7a8d1002f0 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift @@ -214,11 +214,15 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { let optIcon = wallet.info.walletIdenticonData().flatMap { try? iconGenerator.generateFromAccountId($0) } let iconViewModel = optIcon.map { DrawableIconViewModel(icon: $0) } + let totalAmountViewModel = WalletTotalAmountView.ViewModel( + icon: iconViewModel, + name: wallet.info.name, + amount: totalValue + ) + return WalletsListViewModel( identifier: wallet.identifier, - name: wallet.info.name, - icon: iconViewModel, - value: totalValue, + walletAmountViewModel: totalAmountViewModel, isSelected: wallet.isSelected ) } diff --git a/novawallet/Modules/WalletsList/Manage/WalletManageTableViewCell.swift b/novawallet/Modules/WalletsList/Manage/WalletManageTableViewCell.swift index 3c9f25ca6a..9b3a7c2aa2 100644 --- a/novawallet/Modules/WalletsList/Manage/WalletManageTableViewCell.swift +++ b/novawallet/Modules/WalletsList/Manage/WalletManageTableViewCell.swift @@ -1,17 +1,19 @@ import UIKit import SoraUI -final class WalletManageTableViewCell: WalletsListTableViewCell { +final class WalletManageTableViewCell: WalletsListTableViewCell { private lazy var reorderingAnimator = BlockViewAnimator() - let disclosureIndicatorView: UIImageView = { - let imageView = UIImageView() + var disclosureIndicatorView: UIImageView { contentDisplayView.valueView } + + override func setupStyle() { + super.setupStyle() + let icon = R.image.iconSmallArrow()?.tinted(with: R.color.colorTextSecondary()!) - imageView.image = icon - imageView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal) - return imageView - }() + disclosureIndicatorView.image = icon + disclosureIndicatorView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + disclosureIndicatorView.setContentHuggingPriority(.defaultHigh, for: .horizontal) + } func setReordering(_ reordering: Bool, animated: Bool) { let closure = { @@ -28,15 +30,4 @@ final class WalletManageTableViewCell: WalletsListTableViewCell { recolorReorderControl(R.color.colorIconPrimary()!) } } - - override func setupLayout() { - super.setupLayout() - - contentView.addSubview(disclosureIndicatorView) - disclosureIndicatorView.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(UIConstants.horizontalInset) - make.centerY.equalToSuperview() - make.leading.equalTo(infoView.snp.trailing).offset(8.0) - } - } } diff --git a/novawallet/Modules/WalletsList/Selection/WalletSelectionTableViewCell.swift b/novawallet/Modules/WalletsList/Selection/WalletSelectionTableViewCell.swift index f46d8b426d..65b27f08b5 100644 --- a/novawallet/Modules/WalletsList/Selection/WalletSelectionTableViewCell.swift +++ b/novawallet/Modules/WalletsList/Selection/WalletSelectionTableViewCell.swift @@ -1,7 +1,7 @@ import Foundation -final class WalletSelectionTableViewCell: WalletsListTableViewCell { - let selectorView = RadioSelectorView() +final class WalletSelectionTableViewCell: WalletsListTableViewCell { + var selectorView: RadioSelectorView { contentDisplayView.valueView } override func bind(viewModel: WalletsListViewModel) { super.bind(viewModel: viewModel) @@ -14,12 +14,8 @@ final class WalletSelectionTableViewCell: WalletsListTableViewCell { let selectorSize = 2 * selectorView.outerRadius - contentView.addSubview(selectorView) selectorView.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(16.0) - make.centerY.equalToSuperview() make.size.equalTo(selectorSize) - make.leading.equalTo(infoView.snp.trailing).offset(8.0) } } } diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index 23ae9d15d3..2ab2e2d468 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1218,3 +1218,4 @@ "common.disconnect" = "Disconnect"; "common.status" = "Status"; "common.none" = "None"; +"dapps.missing.required.networks.warning.format" = "Some of the required networks requested by “%@” are not supported in Nova Wallet"; diff --git a/novawallet/en.lproj/Localizable.stringsdict b/novawallet/en.lproj/Localizable.stringsdict index b41ba2928a..95836ad6b1 100644 --- a/novawallet/en.lproj/Localizable.stringsdict +++ b/novawallet/en.lproj/Localizable.stringsdict @@ -146,5 +146,21 @@ %li unsupported + missing.accounts.warning.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %2$@ account is missing. Add account to the wallet in Settings + other + %2$@ accounts are missing. Add accounts to the wallet in Settings + + diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 74219592d7..b967f2121e 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1218,3 +1218,4 @@ "common.disconnect" = "Отключить"; "common.status" = "Статус"; "common.none" = "Отсутствуют"; +"dapps.missing.required.networks.warning.format" = "Некоторые запрошенные “%@” сети не поддерживаются в Nova Wallet"; diff --git a/novawallet/ru.lproj/Localizable.stringsdict b/novawallet/ru.lproj/Localizable.stringsdict index 4bcfa17eef..78a8b35380 100644 --- a/novawallet/ru.lproj/Localizable.stringsdict +++ b/novawallet/ru.lproj/Localizable.stringsdict @@ -174,5 +174,21 @@ %li не поддерживаются + missing.accounts.warning.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %2$@ аккаунт не найден. Добавьте аккаунт в кошелек в Настройках + other + %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках + + diff --git a/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift b/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift new file mode 100644 index 0000000000..8c4d72bd6a --- /dev/null +++ b/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift @@ -0,0 +1,16 @@ +import XCTest + +class DAppWalletAuthTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + XCTFail("Did you forget to add tests?") + } +} diff --git a/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift b/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift index faa7bd1c72..5bf07eaac0 100644 --- a/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift +++ b/novawalletTests/Modules/DApps/DAppAuthConfirm/DAppAuthConfirmTests.swift @@ -111,9 +111,8 @@ class DAppAuthConfirmTests: XCTestCase { origin: "DApp", dApp: "Test", dAppIcon: nil, - requiredChains: [], - optionalChains: nil, - unknownChains: nil + requiredChains: .init(), + optionalChains: nil ) let presenter = DAppAuthConfirmPresenter( From e27322e92af1037998838cdfc3287b22eee70139 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 8 May 2023 15:15:29 +0500 Subject: [PATCH 34/54] new auth confirm interface completed --- .../Common/Helpers/BalanceCalculator.swift | 12 +++- novawallet/Common/Helpers/BalancesStore.swift | 25 +++++--- .../WalletAccount/WalletTotalAmountView.swift | 3 + .../DAppInteractionPresenter.swift | 57 ++++++++++++++----- .../DAppWalletAuthInteractor.swift | 41 ++++++++++++- .../DAppWalletAuthPresenter.swift | 10 +++- .../DAppWalletAuthProtocols.swift | 6 +- .../DAppWalletAuthViewController.swift | 4 ++ .../DAppWalletAuthViewFactory.swift | 21 ++++++- .../DAppWalletAuthViewLayout.swift | 8 ++- .../ViewModel/DAppWalletAuthViewModel.swift | 2 +- .../DAppWalletAuthViewModelFactory.swift | 2 +- 12 files changed, 158 insertions(+), 33 deletions(-) diff --git a/novawallet/Common/Helpers/BalanceCalculator.swift b/novawallet/Common/Helpers/BalanceCalculator.swift index ee5596ea5e..ed1ce93b2b 100644 --- a/novawallet/Common/Helpers/BalanceCalculator.swift +++ b/novawallet/Common/Helpers/BalanceCalculator.swift @@ -140,7 +140,7 @@ final class BalancesCalculator { ) let contributions = crowdloanContributions[accountId]? - .filter { !chainAccountIds.contains($0.key) } ?? [:] + .filter { !excludingChainIds.contains($0.key) } ?? [:] let crowdloans = calculateCrowdloanContribution( contributions, @@ -159,11 +159,17 @@ extension BalancesCalculator: BalancesCalculating { var totalValue: Decimal = 0.0 if let substrateAccountId = wallet.substrateAccountId { - totalValue += calculateTotalValue(for: substrateAccountId, excludingChainIds: chainAccountIds) + totalValue += calculateTotalValue( + for: substrateAccountId, + excludingChainIds: Set(chainAccountIds) + ) } if let ethereumAddress = wallet.ethereumAddress { - totalValue += calculateTotalValue(for: ethereumAddress, excludingChainIds: chainAccountIds) + totalValue += calculateTotalValue( + for: ethereumAddress, + excludingChainIds: Set(chainAccountIds) + ) } wallet.chainAccounts.forEach { chainAccount in diff --git a/novawallet/Common/Helpers/BalancesStore.swift b/novawallet/Common/Helpers/BalancesStore.swift index ae201c6b31..ca760259d5 100644 --- a/novawallet/Common/Helpers/BalancesStore.swift +++ b/novawallet/Common/Helpers/BalancesStore.swift @@ -60,6 +60,7 @@ final class BalancesStore { chainRegistry.chainsSubscribe(self, runningInQueue: .main) { [weak self] changes in self?.calculator?.didReceiveChainChanges(changes) self?.handle(changes: changes) + self?.notifyCalculatorChanges() } } @@ -91,6 +92,16 @@ final class BalancesStore { } } + private func notifyCalculatorChanges() { + if let calculator = calculator { + delegate?.balancesStore(self, didUpdate: calculator) + } + } + + private func notify(error: BalancesStoreError) { + delegate?.balancesStore(self, didReceive: error) + } + private func updatePriceProvider( for priceIdSet: Set, currency: Currency @@ -120,16 +131,14 @@ final class BalancesStore { ) strongSelf.calculator?.didReceivePrice(mappedChanges) + strongSelf.notifyCalculatorChanges() return } let failureClosure = { [weak self] (error: Error) in - guard let strongSelf = self else { - return - } - - strongSelf.delegate?.balancesStore(strongSelf, didReceive: .priceFailed(error)) + self?.notify(error: .priceFailed(error)) + return } let options = StreamableProviderObserverOptions( @@ -166,8 +175,9 @@ extension BalancesStore: WalletLocalStorageSubscriber, WalletLocalSubscriptionHa switch result { case let .success(changes): calculator?.didReceiveBalancesChanges(changes) + notifyCalculatorChanges() case let .failure(error): - delegate?.balancesStore(self, didReceive: .balancesFailed(error)) + notify(error: .balancesFailed(error)) } } } @@ -177,8 +187,9 @@ extension BalancesStore: CrowdloanContributionLocalSubscriptionHandler, Crowdloa switch result { case let .success(changes): calculator?.didReceiveCrowdloanContributionChanges(changes) + notifyCalculatorChanges() case let .failure(error): - delegate?.balancesStore(self, didReceive: .crowdloansFailed(error)) + notify(error: .crowdloansFailed(error)) } } } diff --git a/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift b/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift index e739139189..477f29cf4f 100644 --- a/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift +++ b/novawallet/Common/View/WalletAccount/WalletTotalAmountView.swift @@ -18,7 +18,10 @@ final class WalletTotalAmountView: IconDetailsGenericView { detailsView.spacing = 0 titleLabel.apply(style: .regularSubhedlinePrimary) + titleLabel.textAlignment = .left + subtitleLabel.apply(style: .footnoteSecondary) + subtitleLabel.textAlignment = .left } } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift index 72e1d4bfc4..bd92a77e6a 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift @@ -11,6 +11,45 @@ final class DAppInteractionPresenter { init(logger: LoggerProtocol) { self.logger = logger } + + private func presentDefaultAuthConfirmation(for request: DAppAuthRequest) { + guard let authVew = DAppAuthConfirmViewFactory.createView(for: request, delegate: self) else { + return + } + + let factory = ModalSheetPresentationFactory( + configuration: ModalSheetPresentationConfiguration.nova + ) + authVew.controller.modalTransitioningFactory = factory + authVew.controller.modalPresentationStyle = .custom + + window?.rootViewController?.topModalViewController.present( + authVew.controller, + animated: true, + completion: nil + ) + } + + private func presentWalletConnectAuthConfirmation(for request: DAppAuthRequest) { + guard + let confirmationView = DAppWalletAuthViewFactory.createWalletConnectView( + for: request, + delegate: self + ) else { + return + } + + let navigationController = NovaNavigationController(rootViewController: confirmationView.controller) + navigationController.barSettings = .init(style: .defaultStyle, shouldSetCloseButton: false) + + navigationController.modalPresentationStyle = .overFullScreen + + window?.rootViewController?.topModalViewController.present( + navigationController, + animated: true, + completion: nil + ) + } } extension DAppInteractionPresenter: DAppInteractionOutputProtocol { @@ -37,21 +76,11 @@ extension DAppInteractionPresenter: DAppInteractionOutputProtocol { } func didReceiveAuth(request: DAppAuthRequest) { - guard let authVew = DAppAuthConfirmViewFactory.createView(for: request, delegate: self) else { - return + if request.transportName == DAppTransports.walletConnect { + presentWalletConnectAuthConfirmation(for: request) + } else { + presentDefaultAuthConfirmation(for: request) } - - let factory = ModalSheetPresentationFactory( - configuration: ModalSheetPresentationConfiguration.nova - ) - authVew.controller.modalTransitioningFactory = factory - authVew.controller.modalPresentationStyle = .custom - - window?.rootViewController?.topModalViewController.present( - authVew.controller, - animated: true, - completion: nil - ) } func didDetectPhishing(host _: String) { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift index 60254d24a3..8cef8cb6c4 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthInteractor.swift @@ -2,8 +2,47 @@ import UIKit final class DAppWalletAuthInteractor { weak var presenter: DAppWalletAuthInteractorOutputProtocol? + + let balancesStore: BalancesStoreProtocol + + private var wallet: MetaAccountModel? + private var calculator: BalancesCalculating? + + init(balancesStore: BalancesStoreProtocol) { + self.balancesStore = balancesStore + } + + private func provideTotalValue() { + guard let wallet = wallet, let calculator = calculator else { + return + } + + let totalValue = calculator.calculateTotalValue(for: wallet) + presenter?.didFetchTotalValue(totalValue, wallet: wallet) + } } extension DAppWalletAuthInteractor: DAppWalletAuthInteractorInputProtocol { - func fetchTotalValue(for _: MetaAccountModel) {} + func setup() { + balancesStore.delegate = self + balancesStore.setup() + } + + func apply(wallet: MetaAccountModel) { + self.wallet = wallet + + provideTotalValue() + } +} + +extension DAppWalletAuthInteractor: BalancesStoreDelegate { + func balancesStore(_: BalancesStoreProtocol, didUpdate calculator: BalancesCalculating) { + self.calculator = calculator + + provideTotalValue() + } + + func balancesStore(_: BalancesStoreProtocol, didReceive error: BalancesStoreError) { + presenter?.didReceive(error: error) + } } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift index 7c0ae4df62..18d28f547b 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift @@ -58,7 +58,8 @@ extension DAppWalletAuthPresenter: DAppWalletAuthPresenterProtocol { func setup() { updateView() - interactor.fetchTotalValue(for: selectedWallet) + interactor.setup() + interactor.apply(wallet: selectedWallet) } func approve() { @@ -72,6 +73,8 @@ extension DAppWalletAuthPresenter: DAppWalletAuthPresenterProtocol { extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { func didFetchTotalValue(_ value: Decimal, wallet: MetaAccountModel) { + logger.debug("Did receive total value: \(value)") + guard wallet.metaId == selectedWallet.metaId else { return } @@ -80,6 +83,11 @@ extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { updateView() } + + func didReceive(error: BalancesStoreError) { + // ignore the because a user can't fix storage problems + logger.error("Did receive error: \(error)") + } } extension DAppWalletAuthPresenter: Localizable { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift index 6805d50672..185d010202 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift @@ -1,3 +1,5 @@ +import SoraFoundation + protocol DAppWalletAuthViewProtocol: ControllerBackedProtocol { func didReceive(viewModel: DAppWalletAuthViewModel) } @@ -9,11 +11,13 @@ protocol DAppWalletAuthPresenterProtocol: AnyObject { } protocol DAppWalletAuthInteractorInputProtocol: AnyObject { - func fetchTotalValue(for wallet: MetaAccountModel) + func setup() + func apply(wallet: MetaAccountModel) } protocol DAppWalletAuthInteractorOutputProtocol: AnyObject { func didFetchTotalValue(_ value: Decimal, wallet: MetaAccountModel) + func didReceive(error: BalancesStoreError) } protocol DAppWalletAuthWireframeProtocol: AnyObject { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift index 4243cd62f9..2bf04ec8fb 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift @@ -32,6 +32,8 @@ final class DAppWalletAuthViewController: UIViewController, ViewHolder { override func viewDidLoad() { super.viewDidLoad() + setupLocalization() + presenter.setup() } @@ -41,6 +43,8 @@ final class DAppWalletAuthViewController: UIViewController, ViewHolder { title = localizableTitle.value(for: selectedLocale) rootView.subtitleLabel.text = R.string.localizable.dappAuthSubtitle(preferredLanguages: languages) rootView.dappCell.titleLabel.text = R.string.localizable.commonDapp(preferredLanguages: languages) + + setupButtonsLocalization() } private func setupButtonsLocalization() { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift index f6b665b48d..a89cf70fb6 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift @@ -18,11 +18,12 @@ struct DAppWalletAuthViewFactory { delegate: DAppAuthDelegate, title: LocalizableResource ) -> DAppWalletAuthViewProtocol? { - guard let currencyManager = CurrencyManager.shared else { + guard + let currencyManager = CurrencyManager.shared, + let interactor = createInteractor() else { return nil } - let interactor = DAppWalletAuthInteractor() let wireframe = DAppWalletAuthWireframe() let localizationManager = LocalizationManager.shared @@ -57,4 +58,20 @@ struct DAppWalletAuthViewFactory { return view } + + private static func createInteractor() -> DAppWalletAuthInteractor? { + guard let currencyManager = CurrencyManager.shared else { + return nil + } + + let balancesStore = BalancesStore( + chainRegistry: ChainRegistryFacade.sharedRegistry, + walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, + priceLocalSubscriptionFactory: PriceProviderFactory.shared, + currencyManager: currencyManager, + crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactory.shared + ) + + return .init(balancesStore: balancesStore) + } } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift index 56de697291..adac9b8120 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewLayout.swift @@ -29,7 +29,10 @@ final class DAppWalletAuthViewLayout: SCGenericActionLayoutView { let dappCell = StackTableCell() let networksCell = StackInfoTableCell() - let walletTableView = StackTableView() + let walletTableView: StackTableView = .create { view in + view.cellHeight = 56 + } + let walletCell = StackWalletAmountCell() private(set) var rejectButton: TriangularedButton? @@ -56,7 +59,7 @@ final class DAppWalletAuthViewLayout: SCGenericActionLayoutView { } let button = TriangularedButton() - button.applyEnabledStyle() + button.applyDefaultStyle() genericActionView.addArrangedSubview(button) approveButton = button @@ -89,6 +92,7 @@ final class DAppWalletAuthViewLayout: SCGenericActionLayoutView { super.setupLayout() genericActionView.axis = .horizontal + genericActionView.distribution = .fillEqually genericActionView.spacing = 16 let headerView = UIView() diff --git a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift index 86e62e349f..b9217e4612 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModel.swift @@ -11,6 +11,6 @@ struct DAppWalletAuthViewModel { let walletWarning: String? var canApprove: Bool { - networksWarning != nil || walletWarning != nil + networksWarning == nil && walletWarning == nil } } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift index 16671f9606..f497e25c10 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/ViewModel/DAppWalletAuthViewModelFactory.swift @@ -51,7 +51,7 @@ final class DAppWalletAuthViewModelFactory { from request: DAppAuthRequest, locale: Locale ) -> String? { - guard !request.requiredChains.hasUnresolved else { + guard request.requiredChains.hasUnresolved else { return nil } From c4def8a8d68a74fbd3c1a516c08d7e932deafdc9 Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 8 May 2023 17:47:29 +0500 Subject: [PATCH 35/54] select wallet during wallet connect auth --- novawallet.xcodeproj/project.pbxproj | 48 +++++ .../Helpers/BalancesStore+Default.swift | 17 ++ .../Protocols/WalletChoosePresentable.swift | 35 +++ .../DAppWalletAuthPresenter.swift | 23 ++ .../DAppWalletAuthProtocols.swift | 4 +- .../DAppWalletAuthViewController.swift | 23 ++ .../DAppWalletAuthViewFactory.swift | 10 +- .../DAppWalletAuthWireframe.swift | 18 ++ .../Choose/WalletsChoosePresenter.swift | 39 ++++ .../Choose/WalletsChooseProtocols.swift | 7 + .../Choose/WalletsChooseViewController.swift | 23 ++ .../Choose/WalletsChooseViewFactory.swift | 54 +++++ .../WalletsChooseViewModelFactory.swift | 24 +++ .../Common/WalletsListInteractor.swift | 159 ++------------ .../Common/WalletsListPresenter.swift | 95 +------- .../Common/WalletsListProtocols.swift | 5 +- .../Common/WalletsListViewModelFactory.swift | 204 ++---------------- .../Manage/WalletManageInteractor.swift | 14 +- .../Manage/WalletManageViewFactory.swift | 9 +- .../Selection/WalletSelectionInteractor.swift | 15 +- .../WalletSelectionViewFactory.swift | 10 +- .../WalletsChoose/WalletsChooseTests.swift | 16 ++ 22 files changed, 392 insertions(+), 460 deletions(-) create mode 100644 novawallet/Common/Helpers/BalancesStore+Default.swift create mode 100644 novawallet/Common/Protocols/WalletChoosePresentable.swift create mode 100644 novawallet/Modules/WalletsList/Choose/WalletsChoosePresenter.swift create mode 100644 novawallet/Modules/WalletsList/Choose/WalletsChooseProtocols.swift create mode 100644 novawallet/Modules/WalletsList/Choose/WalletsChooseViewController.swift create mode 100644 novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift create mode 100644 novawallet/Modules/WalletsList/Choose/WalletsChooseViewModelFactory.swift create mode 100644 novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 0b76504844..0f20ebab60 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ 3B7EEC888C19F954B5EB1012 /* OnChainTransferSetupWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9871C4FF3B05F6055AF82F14 /* OnChainTransferSetupWireframe.swift */; }; 3B87871B471FF8BA84DC7910 /* ParaStkYieldBoostStopWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285212895DBAB0098F302DF9 /* ParaStkYieldBoostStopWireframe.swift */; }; 3BFD635E852E4D395025BEE8 /* ParaStkCollatorsSearchViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C602661DE4D6CAC482AF721 /* ParaStkCollatorsSearchViewFactory.swift */; }; + 3C6C738F4AB7AC6FEA290D59 /* WalletsChoosePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525813EB768E636A397C00BB /* WalletsChoosePresenter.swift */; }; 3CA86739CB09801714B194BD /* PurchaseWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C52C6CD7112BF0E1E3A98CE /* PurchaseWireframe.swift */; }; 3D1FB0EF87D42F08D9250552 /* PurchasePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF8DBE5C32EE4C68ECD623 /* PurchasePresenter.swift */; }; 3D61F07E10ED522E389B2192 /* DelegateVotedReferendaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2883649A5E84DF9B861C83 /* DelegateVotedReferendaViewController.swift */; }; @@ -1029,6 +1030,10 @@ 844C3E6B2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6A2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift */; }; 844C3E6D2A08E1B300C4305F /* BalancesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */; }; 844C3E6F2A08E74D00C4305F /* BalanceCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */; }; + 844C3E712A09126F00C4305F /* WalletsChooseViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E702A09126F00C4305F /* WalletsChooseViewFactory.swift */; }; + 844C3E732A09184300C4305F /* BalancesStore+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E722A09184300C4305F /* BalancesStore+Default.swift */; }; + 844C3E752A091B9800C4305F /* WalletsChooseViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E742A091B9800C4305F /* WalletsChooseViewModelFactory.swift */; }; + 844C3E772A09228200C4305F /* WalletChoosePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */; }; 844CB56226F943AD00396E13 /* WalletLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */; }; 844CB56426F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */; }; 844CB56826F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */; }; @@ -2907,6 +2912,7 @@ 90ACE8690DA095E4F45494E9 /* TransferConfirmProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7865BACFB8591F67D8EE06 /* TransferConfirmProtocols.swift */; }; 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA3BF0C9C1E0E2C67D962F5 /* PurchaseViewFactory.swift */; }; 91201789084DD9A419CA8CD3 /* MarkdownDescriptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F080BC55D9575EBE4216283C /* MarkdownDescriptionViewController.swift */; }; + 912812D2C46BC18F5C1C14F9 /* WalletsChooseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */; }; 912ECC319A48CAD09FB694AC /* AssetsSearchProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9F9E43E296386A7F138326 /* AssetsSearchProtocols.swift */; }; 91530F7301CA39654E008580 /* DAppBrowserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A4DFCC4236D25A3D59F809 /* DAppBrowserInteractor.swift */; }; 91A1286763617DE022BD495F /* LedgerInstructionsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B08ACC71BE679A48A7B66E /* LedgerInstructionsPresenter.swift */; }; @@ -3186,6 +3192,7 @@ CD9359A2720F2EE1D4E09DF6 /* DAppTxDetailsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219B9B1D97460F022D40D63E /* DAppTxDetailsWireframe.swift */; }; CDAB179209D12B81430E377C /* LedgerAccountConfirmationViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A695CA303926DFB5D54E309 /* LedgerAccountConfirmationViewLayout.swift */; }; CDB78A5A733E4A4F1A2C48C8 /* AssetSelectionWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AD1285797131E836CD994B /* AssetSelectionWireframe.swift */; }; + CDD016AB97CF4619B9A17B3E /* WalletsChooseProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22EEDCFFB7BB31BC71F651DE /* WalletsChooseProtocols.swift */; }; CDED41B125E1D5128736B933 /* ParitySignerTxScanViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CD8C618D61C78EA8C58532 /* ParitySignerTxScanViewLayout.swift */; }; CE2792E78B14CE02394D8CF4 /* ReferralCrowdloanViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594BC61689EC942ED0A64A4A /* ReferralCrowdloanViewLayout.swift */; }; CE4C1344F03A5132C601A594 /* LocksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D4D2E89D40718677685CE1 /* LocksViewController.swift */; }; @@ -3244,6 +3251,7 @@ DCE9FE8A75C2FE7B5CB92CC2 /* LedgerWalletConfirmWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8167536168325942DA6892E1 /* LedgerWalletConfirmWireframe.swift */; }; DD090C2ED91726FF7779F6C7 /* WalletHistoryFilterViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB10C8DA56E92C280D66BE8 /* WalletHistoryFilterViewFactory.swift */; }; DD35DDD9114DF482D04F7498 /* GovernanceRevokeDelegationConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983BF144ACC64123AE37130D /* GovernanceRevokeDelegationConfirmPresenter.swift */; }; + DD3EB45488B37F390718F407 /* WalletsChooseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3C7EFCD6663C736563688A /* WalletsChooseViewController.swift */; }; DDA07514BEF3E2FD6EE1BB4E /* InAppUpdatesViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06268C2A9EAC65722D6E8947 /* InAppUpdatesViewLayout.swift */; }; DE03CA5AD7F1D0B80DFF13B6 /* DAppBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855972C88E512AED37FD312 /* DAppBrowserViewController.swift */; }; DE52F23521D54A07F558EB1B /* ReferendumVoteConfirmInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1A35F4D82F97C9663F1CD4 /* ReferendumVoteConfirmInteractor.swift */; }; @@ -3572,6 +3580,7 @@ 21A2FD02269432066884F5AF /* ParaStkYourCollatorsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkYourCollatorsInteractor.swift; sourceTree = ""; }; 21BCBFCBE606A354CB652289 /* DAppAuthConfirmViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAuthConfirmViewController.swift; sourceTree = ""; }; 22914482F786318D8F6C5E27 /* DAppPhishingViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppPhishingViewLayout.swift; sourceTree = ""; }; + 22EEDCFFB7BB31BC71F651DE /* WalletsChooseProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsChooseProtocols.swift; sourceTree = ""; }; 2336E4CAF4A1F627C39093FF /* ParaStkSelectCollatorsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkSelectCollatorsViewFactory.swift; sourceTree = ""; }; 2377F8FB07B47637346249F5 /* StakingRewardDetailsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardDetailsViewFactory.swift; sourceTree = ""; }; 23A74BDB54D503FA2BFBEF35 /* StakingUnbondSetupProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingUnbondSetupProtocols.swift; sourceTree = ""; }; @@ -3745,6 +3754,7 @@ 5147BFCC44EB3938D50EE8D9 /* DAppPhishingPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppPhishingPresenter.swift; sourceTree = ""; }; 5159EA2661A6CBE123CCF891 /* ReferendumVoteSetupProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteSetupProtocols.swift; sourceTree = ""; }; 518305BB475DE40E94DCBD5D /* DAppPhishingWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppPhishingWireframe.swift; sourceTree = ""; }; + 525813EB768E636A397C00BB /* WalletsChoosePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsChoosePresenter.swift; sourceTree = ""; }; 5278A5F4178922A240590334 /* DAppBrowserViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserViewLayout.swift; sourceTree = ""; }; 52B7577593D1A0789B60FF70 /* NftListViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftListViewLayout.swift; sourceTree = ""; }; 52BF275FC06E8B06FA4D4719 /* LedgerInstructionsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LedgerInstructionsViewController.swift; sourceTree = ""; }; @@ -4515,6 +4525,10 @@ 844C3E6A2A08C05A00C4305F /* DAppWalletAuthViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthViewModelFactory.swift; sourceTree = ""; }; 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalancesStore.swift; sourceTree = ""; }; 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceCalculator.swift; sourceTree = ""; }; + 844C3E702A09126F00C4305F /* WalletsChooseViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletsChooseViewFactory.swift; sourceTree = ""; }; + 844C3E722A09184300C4305F /* BalancesStore+Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BalancesStore+Default.swift"; sourceTree = ""; }; + 844C3E742A091B9800C4305F /* WalletsChooseViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletsChooseViewModelFactory.swift; sourceTree = ""; }; + 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletChoosePresentable.swift; sourceTree = ""; }; 844CB56126F943AD00396E13 /* WalletLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56326F945E200396E13 /* SubstrateLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateLocalSubscriptionFactory.swift; sourceTree = ""; }; 844CB56726F9BF9800396E13 /* CrowdloanLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanLocalSubscriptionFactory.swift; sourceTree = ""; }; @@ -6403,6 +6417,7 @@ 8B56BDC7E6221DE292498D3A /* ParitySignerTxQrViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParitySignerTxQrViewFactory.swift; sourceTree = ""; }; 8CBB8745F8C36BB107625E8F /* ParitySignerWelcomeProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParitySignerWelcomeProtocols.swift; sourceTree = ""; }; 8CFD9A58CDDA60D9E1204078 /* ParaStkCollatorsSearchProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkCollatorsSearchProtocols.swift; sourceTree = ""; }; + 8D3C7EFCD6663C736563688A /* WalletsChooseViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsChooseViewController.swift; sourceTree = ""; }; 8D51D60F19284936A6E9F47D /* ReferralCrowdloanWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferralCrowdloanWireframe.swift; sourceTree = ""; }; 8D913590BB0E9435259719CD /* WalletsListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsListViewController.swift; sourceTree = ""; }; 8E1179D4A18F46C75B19CAC2 /* ParaStkRedeemProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkRedeemProtocols.swift; sourceTree = ""; }; @@ -6954,6 +6969,7 @@ FCA43AABAC7555C2E648442F /* DAppSettingsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSettingsViewFactory.swift; sourceTree = ""; }; FE0641B2354E6F236CB9A132 /* NftDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftDetailsPresenter.swift; sourceTree = ""; }; FE4AF0849E32E5B9C72E2ABB /* RecommendedValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewFactory.swift; sourceTree = ""; }; + FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsChooseTests.swift; sourceTree = ""; }; FF4688AF0658F8BB7A90C2BE /* ExportMnemonicConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicConfirmViewFactory.swift; sourceTree = ""; }; FF755EE09598254BB5E59CC2 /* ReferendumVoteSetupViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteSetupViewFactory.swift; sourceTree = ""; }; FFB4A14C99D151B41F61F474 /* DAppTxDetailsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppTxDetailsInteractor.swift; sourceTree = ""; }; @@ -7487,6 +7503,18 @@ path = DelegateInfo; sourceTree = ""; }; + 34CB1A3176E8B08DEFB58800 /* Choose */ = { + isa = PBXGroup; + children = ( + 22EEDCFFB7BB31BC71F651DE /* WalletsChooseProtocols.swift */, + 525813EB768E636A397C00BB /* WalletsChoosePresenter.swift */, + 8D3C7EFCD6663C736563688A /* WalletsChooseViewController.swift */, + 844C3E702A09126F00C4305F /* WalletsChooseViewFactory.swift */, + 844C3E742A091B9800C4305F /* WalletsChooseViewModelFactory.swift */, + ); + path = Choose; + sourceTree = ""; + }; 34FF81AA0EBFAB3390FD989D /* TransferConfirm */ = { isa = PBXGroup; children = ( @@ -7614,6 +7642,7 @@ 486ADD5F84F3B18E6F5BC0DA /* WalletsList */ = { isa = PBXGroup; children = ( + 34CB1A3176E8B08DEFB58800 /* Choose */, 84EE2FA32891203700A98816 /* Manage */, 842EBB2D289096FC00B952D8 /* Selection */, 842EBB2C289096D300B952D8 /* Common */, @@ -11135,6 +11164,7 @@ 84ADA60F29B9E2E800EB687E /* MultiExtrinsicRetryable.swift */, 8495C6EA29BA24350014293F /* MessageSheetPresentable+ErrorPresentable.swift */, 8846F71F29D56A0700B8B776 /* Web3NameAddressListPresentable.swift */, + 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */, ); path = Protocols; sourceTree = ""; @@ -11260,6 +11290,7 @@ 843F9ABB29DDAF8F004F1737 /* JSONRPCTimeout.swift */, 844C3E6C2A08E1B300C4305F /* BalancesStore.swift */, 844C3E6E2A08E74D00C4305F /* BalanceCalculator.swift */, + 844C3E722A09184300C4305F /* BalancesStore+Default.swift */, ); path = Helpers; sourceTree = ""; @@ -12388,6 +12419,7 @@ 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, 312F85F94736E09922F5733F /* DAppWalletAuth */, + BB3F7FDC375F90A4BE705D5E /* WalletsChoose */, ); path = Modules; sourceTree = ""; @@ -15810,6 +15842,14 @@ path = StakingRewardPayouts; sourceTree = ""; }; + BB3F7FDC375F90A4BE705D5E /* WalletsChoose */ = { + isa = PBXGroup; + children = ( + FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */, + ); + path = WalletsChoose; + sourceTree = ""; + }; BF6F50DD15230CADAC713359 /* AccountImport */ = { isa = PBXGroup; children = ( @@ -19102,6 +19142,7 @@ 849ABE7C2628116F00011A2A /* NominationsReducer.swift in Sources */, 846CA77E2709A34D0011124C /* StakingAnalyticsLocalStorageSubscriber.swift in Sources */, A8F69AC9D7294E7DCBA50470 /* SelectValidatorsStartPresenter.swift in Sources */, + 844C3E772A09228200C4305F /* WalletChoosePresentable.swift in Sources */, 84AE7AB127D38C9400495267 /* DisplayAddressViewModel.swift in Sources */, 84282296289BC31300163031 /* AddAccount+ParitySignerAddConfirmWireframe.swift in Sources */, 84FB9E1E285C58FF00B42FC0 /* Xcm.swift in Sources */, @@ -19181,6 +19222,7 @@ 84EBFCEE285E82BB0006327E /* XcmExecute.swift in Sources */, 849976C627B2B73900B14A6C /* DAppMetamaskStateMachine.swift in Sources */, 88F34FDF28FFEAE500712BDE /* TimelineRow.swift in Sources */, + 844C3E732A09184300C4305F /* BalancesStore+Default.swift in Sources */, 847F2D4427A9822200AFD476 /* AssetIconView.swift in Sources */, 20B2942A4241F6713A1C70D9 /* StakingRewardDetailsViewFactory.swift in Sources */, 84F13F0A26F14122006725FF /* ChainAsset.swift in Sources */, @@ -19447,6 +19489,7 @@ 8882FC6529E5D09300DDA90B /* EquillibriumAssetsBalanceUpdater.swift in Sources */, 84B73AE0279E6A600071AE16 /* FeeMetadataContext.swift in Sources */, 842A737527DB8338006EE1EA /* OperationDetailsViewModelFactory.swift in Sources */, + 844C3E712A09126F00C4305F /* WalletsChooseViewFactory.swift in Sources */, 93434E8E407A6C63D8862A21 /* AssetSelectionProtocols.swift in Sources */, 84E25BEC27E87D5400290BF1 /* TransferDataValidatorFactory.swift in Sources */, 8499FE7127BE214A00712589 /* StorageKeyDecodingOperation.swift in Sources */, @@ -19787,6 +19830,7 @@ 413CCB7C7B22831147B8E815 /* ParaStkRedeemPresenter.swift in Sources */, 35F9157CAA182493B2F0E1D3 /* ParaStkRedeemInteractor.swift in Sources */, C729BF3E60E6825AEED11383 /* ParaStkRedeemViewController.swift in Sources */, + 844C3E752A091B9800C4305F /* WalletsChooseViewModelFactory.swift in Sources */, E477B09B47A3021EF1CE66F0 /* ParaStkRedeemViewLayout.swift in Sources */, E0CBD1D747361D121555FD51 /* ParaStkRedeemViewFactory.swift in Sources */, EDD5551608E7ACDDBBC054C4 /* ParaStkRebondProtocols.swift in Sources */, @@ -20240,6 +20284,9 @@ A5153C322938579FA145742A /* DAppWalletAuthViewController.swift in Sources */, D7D91A2CACE6FE12AE634BEF /* DAppWalletAuthViewLayout.swift in Sources */, 3F3AE7490C59A0CE0BF2D7A7 /* DAppWalletAuthViewFactory.swift in Sources */, + CDD016AB97CF4619B9A17B3E /* WalletsChooseProtocols.swift in Sources */, + 3C6C738F4AB7AC6FEA290D59 /* WalletsChoosePresenter.swift in Sources */, + DD3EB45488B37F390718F407 /* WalletsChooseViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -20401,6 +20448,7 @@ 84B7C720289BFA79001A3566 /* ReferralCrowdloanTests.swift in Sources */, F4897BB126AED13D0075F291 /* EraCountdownOperationFactoryStub.swift in Sources */, 1FF9D36D42669B4C1122B533 /* DAppWalletAuthTests.swift in Sources */, + 912812D2C46BC18F5C1C14F9 /* WalletsChooseTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawallet/Common/Helpers/BalancesStore+Default.swift b/novawallet/Common/Helpers/BalancesStore+Default.swift new file mode 100644 index 0000000000..5025eb4845 --- /dev/null +++ b/novawallet/Common/Helpers/BalancesStore+Default.swift @@ -0,0 +1,17 @@ +import Foundation + +extension BalancesStore { + static func createDefaut() -> BalancesStore? { + guard let currencyManager = CurrencyManager.shared else { + return nil + } + + return BalancesStore( + chainRegistry: ChainRegistryFacade.sharedRegistry, + walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, + priceLocalSubscriptionFactory: PriceProviderFactory.shared, + currencyManager: currencyManager, + crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactory.shared + ) + } +} diff --git a/novawallet/Common/Protocols/WalletChoosePresentable.swift b/novawallet/Common/Protocols/WalletChoosePresentable.swift new file mode 100644 index 0000000000..6391a3caa4 --- /dev/null +++ b/novawallet/Common/Protocols/WalletChoosePresentable.swift @@ -0,0 +1,35 @@ +import Foundation + +protocol WalletChoosePresentable: AnyObject { + func showWalletChoose( + from view: DAppWalletAuthViewProtocol?, + selectedWalletId: String, + delegate: WalletsChooseDelegate + ) + + func closeWalletChoose(on view: ControllerBackedProtocol?, completion: @escaping () -> Void) +} + +extension WalletChoosePresentable { + func showWalletChoose( + from view: ControllerBackedProtocol?, + selectedWalletId: String, + delegate: WalletsChooseDelegate + ) { + guard + let chooseView = WalletsChooseViewFactory.createView( + for: selectedWalletId, + delegate: delegate + ) else { + return + } + + let navigationController = NovaNavigationController(rootViewController: chooseView.controller) + + view?.controller.present(navigationController, animated: true) + } + + func closeWalletChoose(on view: ControllerBackedProtocol?, completion: @escaping () -> Void) { + view?.controller.dismiss(animated: true, completion: completion) + } +} diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift index 18d28f547b..2c424df504 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift @@ -69,6 +69,16 @@ extension DAppWalletAuthPresenter: DAppWalletAuthPresenterProtocol { func reject() { complete(with: false) } + + func selectWallet() { + wireframe.showWalletChoose( + from: view, + selectedWalletId: selectedWallet.metaId, + delegate: self + ) + } + + func showNetworks() {} } extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { @@ -90,6 +100,19 @@ extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { } } +extension DAppWalletAuthPresenter: WalletsChooseDelegate { + func walletChooseDidSelect(item: ManagedMetaAccountModel) { + wireframe.closeWalletChoose(on: view) { [weak self] in + self?.selectedWallet = item.info + self?.totalWalletValue = nil + + self?.updateView() + + self?.interactor.apply(wallet: item.info) + } + } +} + extension DAppWalletAuthPresenter: Localizable { func applyLocalization() { if let view = view, view.isSetup { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift index 185d010202..f5f7ef60f0 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift @@ -8,6 +8,8 @@ protocol DAppWalletAuthPresenterProtocol: AnyObject { func setup() func approve() func reject() + func selectWallet() + func showNetworks() } protocol DAppWalletAuthInteractorInputProtocol: AnyObject { @@ -20,6 +22,6 @@ protocol DAppWalletAuthInteractorOutputProtocol: AnyObject { func didReceive(error: BalancesStoreError) } -protocol DAppWalletAuthWireframeProtocol: AnyObject { +protocol DAppWalletAuthWireframeProtocol: WalletChoosePresentable { func close(from view: DAppWalletAuthViewProtocol?) } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift index 2bf04ec8fb..0d6cfbbcc5 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewController.swift @@ -32,11 +32,26 @@ final class DAppWalletAuthViewController: UIViewController, ViewHolder { override func viewDidLoad() { super.viewDidLoad() + setupStaticHandlers() setupLocalization() presenter.setup() } + private func setupStaticHandlers() { + rootView.walletCell.addTarget( + self, + action: #selector(actionSelectWallet), + for: .touchUpInside + ) + + rootView.networksCell.addTarget( + self, + action: #selector(actionShowNetworks), + for: .touchUpInside + ) + } + private func setupLocalization() { let languages = selectedLocale.rLanguages @@ -78,6 +93,14 @@ final class DAppWalletAuthViewController: UIViewController, ViewHolder { @objc func actionReject() { presenter.reject() } + + @objc func actionSelectWallet() { + presenter.selectWallet() + } + + @objc func actionShowNetworks() { + presenter.showNetworks() + } } extension DAppWalletAuthViewController: DAppWalletAuthViewProtocol { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift index a89cf70fb6..44c634f0ec 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift @@ -60,18 +60,10 @@ struct DAppWalletAuthViewFactory { } private static func createInteractor() -> DAppWalletAuthInteractor? { - guard let currencyManager = CurrencyManager.shared else { + guard let balancesStore = BalancesStore.createDefaut() else { return nil } - let balancesStore = BalancesStore( - chainRegistry: ChainRegistryFacade.sharedRegistry, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, - priceLocalSubscriptionFactory: PriceProviderFactory.shared, - currencyManager: currencyManager, - crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactory.shared - ) - return .init(balancesStore: balancesStore) } } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift index 3f07d77ba9..bc688a65e3 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift @@ -4,4 +4,22 @@ final class DAppWalletAuthWireframe: DAppWalletAuthWireframeProtocol { func close(from view: DAppWalletAuthViewProtocol?) { view?.controller.presentingViewController?.dismiss(animated: true, completion: nil) } + + func showWalletChoose( + from view: DAppWalletAuthViewProtocol?, + selectedWalletId: String, + delegate: WalletsChooseDelegate + ) { + guard + let chooseView = WalletsChooseViewFactory.createView( + for: selectedWalletId, + delegate: delegate + ) else { + return + } + + let navigationController = NovaNavigationController(rootViewController: chooseView.controller) + + view?.controller.present(navigationController, animated: true) + } } diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChoosePresenter.swift b/novawallet/Modules/WalletsList/Choose/WalletsChoosePresenter.swift new file mode 100644 index 0000000000..6721a3b65b --- /dev/null +++ b/novawallet/Modules/WalletsList/Choose/WalletsChoosePresenter.swift @@ -0,0 +1,39 @@ +import Foundation +import SoraFoundation + +final class WalletsChoosePresenter: WalletsListPresenter { + weak var delegate: WalletsChooseDelegate? + + init( + delegate: WalletsChooseDelegate, + interactor: WalletsListInteractorInputProtocol, + wireframe: WalletsListWireframeProtocol, + viewModelFactory: WalletsListViewModelFactoryProtocol, + localizationManager: LocalizationManagerProtocol, + logger: LoggerProtocol + ) { + self.delegate = delegate + + super.init( + baseInteractor: interactor, + baseWireframe: wireframe, + viewModelFactory: viewModelFactory, + localizationManager: localizationManager, + logger: logger + ) + } +} + +extension WalletsChoosePresenter: WalletsChoosePresenterProtocol { + func selectItem(at index: Int, section: Int) { + let viewModel = viewModels[section].items[index] + + guard + !viewModel.isSelected, + let item = walletsList.allItems.first(where: { $0.identifier == viewModel.identifier }) else { + return + } + + delegate?.walletChooseDidSelect(item: item) + } +} diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChooseProtocols.swift b/novawallet/Modules/WalletsList/Choose/WalletsChooseProtocols.swift new file mode 100644 index 0000000000..ff57c552f3 --- /dev/null +++ b/novawallet/Modules/WalletsList/Choose/WalletsChooseProtocols.swift @@ -0,0 +1,7 @@ +protocol WalletsChoosePresenterProtocol: WalletsListPresenterProtocol { + func selectItem(at index: Int, section: Int) +} + +protocol WalletsChooseDelegate: AnyObject { + func walletChooseDidSelect(item: ManagedMetaAccountModel) +} diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChooseViewController.swift b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewController.swift new file mode 100644 index 0000000000..524881d7ae --- /dev/null +++ b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewController.swift @@ -0,0 +1,23 @@ +import UIKit +import SoraFoundation + +final class WalletsChooseViewController: WalletsListViewController< + WalletsListViewLayout, + WalletSelectionTableViewCell +> { + var presenter: WalletsChoosePresenterProtocol? { basePresenter as? WalletsChoosePresenterProtocol } + + init(presenter: WalletsChoosePresenterProtocol, localizationManager: LocalizationManagerProtocol) { + super.init(basePresenter: presenter, localizationManager: localizationManager) + } + + override func setupLocalization() { + title = R.string.localizable.commonSelectWallet(preferredLanguages: selectedLocale.rLanguages) + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + super.tableView(tableView, didSelectRowAt: indexPath) + + presenter?.selectItem(at: indexPath.row, section: indexPath.section) + } +} diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift new file mode 100644 index 0000000000..3608e0aedc --- /dev/null +++ b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift @@ -0,0 +1,54 @@ +import Foundation +import SoraFoundation + +final class WalletsChooseViewFactory { + static func createView( + for selectedWalletId: String, + delegate: WalletsChooseDelegate + ) -> WalletsChooseViewController? { + guard + let interactor = createInteractor(), + let currencyManager = CurrencyManager.shared else { + return nil + } + + let wireframe = WalletsListWireframe() + + let localizationManager = LocalizationManager.shared + + let priceAssetInfoFactory = PriceAssetInfoFactory(currencyManager: currencyManager) + let viewModelFactory = WalletsChooseViewModelFactory( + selectedId: selectedWalletId, + assetBalanceFormatterFactory: AssetBalanceFormatterFactory(), + priceAssetInfoFactory: priceAssetInfoFactory, + currencyManager: currencyManager + ) + + let presenter = WalletsChoosePresenter( + delegate: delegate, + interactor: interactor, + wireframe: wireframe, + viewModelFactory: viewModelFactory, + localizationManager: localizationManager, + logger: Logger.shared + ) + + let view = WalletsChooseViewController(presenter: presenter, localizationManager: localizationManager) + + presenter.baseView = view + interactor.basePresenter = presenter + + return view + } + + private static func createInteractor() -> WalletsListInteractor? { + guard let balancesStore = BalancesStore.createDefaut() else { + return nil + } + + return WalletsListInteractor( + balancesStore: balancesStore, + walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactory.shared + ) + } +} diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChooseViewModelFactory.swift b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewModelFactory.swift new file mode 100644 index 0000000000..76916c0f8e --- /dev/null +++ b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewModelFactory.swift @@ -0,0 +1,24 @@ +import Foundation + +final class WalletsChooseViewModelFactory: WalletsListViewModelFactory { + let selectedId: String + + init( + selectedId: String, + assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol, + priceAssetInfoFactory: PriceAssetInfoFactoryProtocol, + currencyManager: CurrencyManagerProtocol + ) { + self.selectedId = selectedId + + super.init( + assetBalanceFormatterFactory: assetBalanceFormatterFactory, + priceAssetInfoFactory: priceAssetInfoFactory, + currencyManager: currencyManager + ) + } + + override func isSelected(wallet: ManagedMetaAccountModel) -> Bool { + wallet.info.metaId == selectedId + } +} diff --git a/novawallet/Modules/WalletsList/Common/WalletsListInteractor.swift b/novawallet/Modules/WalletsList/Common/WalletsListInteractor.swift index faf0f5ab9c..c2270c7ade 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListInteractor.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListInteractor.swift @@ -4,144 +4,33 @@ import RobinHood class WalletsListInteractor { weak var basePresenter: WalletsListInteractorOutputProtocol? - let chainRegistry: ChainRegistryProtocol - let walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol let walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol - let priceLocalSubscriptionFactory: PriceProviderFactoryProtocol - let crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactoryProtocol + let balancesStore: BalancesStoreProtocol - private(set) var priceSubscription: StreamableProvider? - private(set) var assetsSubscription: StreamableProvider? private(set) var walletsSubscription: StreamableProvider? - private(set) var crowdloansSubscription: StreamableProvider? - private(set) var availableTokenPrice: [ChainAssetId: AssetModel.PriceId] = [:] init( - chainRegistry: ChainRegistryProtocol, - walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, - priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, - currencyManager: CurrencyManagerProtocol, - crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactoryProtocol + balancesStore: BalancesStoreProtocol, + walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol ) { - self.chainRegistry = chainRegistry + self.balancesStore = balancesStore self.walletListLocalSubscriptionFactory = walletListLocalSubscriptionFactory - self.walletLocalSubscriptionFactory = walletLocalSubscriptionFactory - self.priceLocalSubscriptionFactory = priceLocalSubscriptionFactory - self.crowdloansLocalSubscriptionFactory = crowdloansLocalSubscriptionFactory - self.currencyManager = currencyManager } private func subscribeWallets() { walletsSubscription = subscribeAllWalletsProvider() } - private func subscribeAssets() { - assetsSubscription = subscribeAllBalancesProvider() - } - - private func subscribeToCrowdloans() { - crowdloansSubscription = subscribeToAllCrowdloansProvider() - } - - private func subscribeChains() { - chainRegistry.chainsSubscribe(self, runningInQueue: .main) { [weak self] changes in - self?.basePresenter?.didReceiveChainChanges(changes) - self?.handle(changes: changes) - } - } - - private func handle(changes: [DataProviderChange]) { - let prevPrices = availableTokenPrice - for change in changes { - switch change { - case let .insert(chain), let .update(chain): - availableTokenPrice = availableTokenPrice.filter { $0.key.chainId != chain.chainId } - - availableTokenPrice = chain.assets.reduce(into: availableTokenPrice) { result, asset in - guard let priceId = asset.priceId else { - return - } - - let chainAssetId = ChainAssetId(chainId: chain.chainId, assetId: asset.assetId) - result[chainAssetId] = priceId - } - case let .delete(deletedIdentifier): - availableTokenPrice = availableTokenPrice.filter { $0.key.chainId != deletedIdentifier } - } - } - - if prevPrices != availableTokenPrice { - updatePriceProvider( - for: Set(availableTokenPrice.values), - currency: selectedCurrency - ) - } - } - - private func updatePriceProvider( - for priceIdSet: Set, - currency: Currency - ) { - priceSubscription = nil - - let priceIds = Array(priceIdSet).sorted() - - guard !priceIds.isEmpty else { - return - } - - priceSubscription = priceLocalSubscriptionFactory.getAllPricesStreamableProvider( - for: priceIds, - currency: currency - ) - - let updateClosure = { [weak self] (changes: [DataProviderChange]) in - guard let strongSelf = self else { - return - } - - let mappedChanges = changes.reduce( - using: .init(), - availableTokenPrice: strongSelf.availableTokenPrice, - currency: currency - ) - - self?.basePresenter?.didReceivePrice(mappedChanges) - - return - } - - let failureClosure = { [weak self] (error: Error) in - self?.basePresenter?.didReceiveError(error) - return - } - - let options = StreamableProviderObserverOptions( - alwaysNotifyOnRefresh: true, - waitsInProgressSyncOnAdd: false, - initialSize: 0, - refreshWhenEmpty: false - ) - - priceSubscription?.addObserver( - self, - deliverOn: .main, - executing: updateClosure, - failing: failureClosure, - options: options - ) - - priceSubscription?.refresh() + private func setupBalancesStore() { + balancesStore.delegate = self + balancesStore.setup() } } extension WalletsListInteractor: WalletsListInteractorInputProtocol { func setup() { - subscribeChains() - subscribeAssets() subscribeWallets() - subscribeToCrowdloans() + setupBalancesStore() } } @@ -156,34 +45,12 @@ extension WalletsListInteractor: WalletListLocalStorageSubscriber, WalletListLoc } } -extension WalletsListInteractor: WalletLocalStorageSubscriber, WalletLocalSubscriptionHandler { - func handleAllBalances(result: Result<[DataProviderChange], Error>) { - switch result { - case let .success(changes): - basePresenter?.didReceiveBalancesChanges(changes) - case let .failure(error): - basePresenter?.didReceiveError(error) - } - } -} - -extension WalletsListInteractor: SelectedCurrencyDepending { - func applyCurrency() { - guard basePresenter != nil else { - return - } - - updatePriceProvider(for: Set(availableTokenPrice.values), currency: selectedCurrency) +extension WalletsListInteractor: BalancesStoreDelegate { + func balancesStore(_: BalancesStoreProtocol, didUpdate calculator: BalancesCalculating) { + basePresenter?.didUpdateBalancesCalculator(calculator) } -} -extension WalletsListInteractor: CrowdloanContributionLocalSubscriptionHandler, CrowdloansLocalStorageSubscriber { - func handleAllCrowdloans(result: Result<[DataProviderChange], Error>) { - switch result { - case let .success(changes): - basePresenter?.didReceiveCrowdloanContributionChanges(changes) - case let .failure(error): - basePresenter?.didReceiveError(error) - } + func balancesStore(_: BalancesStoreProtocol, didReceive error: BalancesStoreError) { + basePresenter?.didReceiveError(error) } } diff --git a/novawallet/Modules/WalletsList/Common/WalletsListPresenter.swift b/novawallet/Modules/WalletsList/Common/WalletsListPresenter.swift index ed1cba6e6c..82096291ad 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListPresenter.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListPresenter.swift @@ -22,12 +22,7 @@ class WalletsListPresenter { return calculator }() - private var identifierMapping: [String: AssetBalanceId] = [:] - private var balances: [AccountId: [ChainAssetId: BigUInt]] = [:] - private var crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]] = [:] - private var crowdloanContributionsMapping: [String: CrowdloanContributionId] = [:] - private var prices: [ChainAssetId: PriceData] = [:] - private var chains: [ChainModel.Id: ChainModel] = [:] + private var balancesCalculator: BalancesCalculating? init( baseInteractor: WalletsListInteractorInputProtocol, @@ -49,14 +44,13 @@ class WalletsListPresenter { } private func updateViewModels() { - viewModels = viewModelFactory.createSectionViewModels( - for: walletsList.allItems, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, - locale: selectedLocale - ) + if let balancesCalculator = balancesCalculator { + viewModels = viewModelFactory.createSectionViewModels( + for: walletsList.allItems, + balancesCalculator: balancesCalculator, + locale: selectedLocale + ) + } baseView?.didReload() } @@ -91,77 +85,8 @@ extension WalletsListPresenter: WalletsListInteractorOutputProtocol { updateViewModels() } - func didReceiveBalancesChanges(_ changes: [DataProviderChange]) { - for change in changes { - switch change { - case let .insert(item), let .update(item): - var accountBalance = balances[item.accountId] ?? [:] - accountBalance[item.chainAssetId] = item.totalInPlank - balances[item.accountId] = accountBalance - - identifierMapping[item.identifier] = AssetBalanceId( - chainId: item.chainAssetId.chainId, - assetId: item.chainAssetId.assetId, - accountId: item.accountId - ) - case let .delete(deletedIdentifier): - if let accountBalanceId = identifierMapping[deletedIdentifier] { - var accountBalance = balances[accountBalanceId.accountId] - accountBalance?[accountBalanceId.chainAssetId] = nil - balances[accountBalanceId.accountId] = accountBalance - } - - identifierMapping[deletedIdentifier] = nil - } - } - - updateViewModels() - } - - func didReceiveChainChanges(_ changes: [DataProviderChange]) { - chains = changes.mergeToDict(chains) - - updateViewModels() - } - - func didReceivePrice(_ changes: [ChainAssetId: DataProviderChange]) { - prices = changes.reduce(into: prices) { accum, keyValue in - accum[keyValue.key] = keyValue.value.item - } - - updateViewModels() - } - - func didReceiveCrowdloanContributionChanges(_ changes: [DataProviderChange]) { - for change in changes { - switch change { - case let .insert(item), let .update(item): - let previousAmount = crowdloanContributionsMapping[item.identifier]?.amount ?? 0 - var accountCrowdloan = crowdloanContributions[item.accountId] ?? [:] - let value: BigUInt = accountCrowdloan[item.chainId] ?? 0 - accountCrowdloan[item.chainId] = value - previousAmount + item.amount - crowdloanContributions[item.accountId] = accountCrowdloan - crowdloanContributionsMapping[item.identifier] = CrowdloanContributionId( - chainId: item.chainId, - accountId: item.accountId, - amount: item.amount - ) - case let .delete(deletedIdentifier): - if let accountContributionId = crowdloanContributionsMapping[deletedIdentifier] { - var accountContributions = crowdloanContributions[accountContributionId.accountId] - if let contribution = accountContributions?[accountContributionId.chainId], - contribution > accountContributionId.amount { - let newAmount = contribution - accountContributionId.amount - accountContributions?[accountContributionId.chainId] = newAmount - } else { - accountContributions?[accountContributionId.chainId] = nil - } - crowdloanContributions[accountContributionId.accountId] = accountContributions - } - - crowdloanContributionsMapping[deletedIdentifier] = nil - } - } + func didUpdateBalancesCalculator(_ calculator: BalancesCalculating) { + balancesCalculator = calculator updateViewModels() } diff --git a/novawallet/Modules/WalletsList/Common/WalletsListProtocols.swift b/novawallet/Modules/WalletsList/Common/WalletsListProtocols.swift index e16f84fe89..45509b0cbf 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListProtocols.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListProtocols.swift @@ -20,10 +20,7 @@ protocol WalletsListInteractorInputProtocol: AnyObject { protocol WalletsListInteractorOutputProtocol: AnyObject { func didReceiveWalletsChanges(_ changes: [DataProviderChange]) - func didReceiveBalancesChanges(_ changes: [DataProviderChange]) - func didReceiveChainChanges(_ changes: [DataProviderChange]) - func didReceivePrice(_ changes: [ChainAssetId: DataProviderChange]) - func didReceiveCrowdloanContributionChanges(_ changes: [DataProviderChange]) + func didUpdateBalancesCalculator(_ calculator: BalancesCalculating) func didReceiveError(_ error: Error) } diff --git a/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift b/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift index 7a8d1002f0..3c2375916f 100644 --- a/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift +++ b/novawallet/Modules/WalletsList/Common/WalletsListViewModelFactory.swift @@ -6,24 +6,18 @@ import SubstrateSdk protocol WalletsListViewModelFactoryProtocol { func createSectionViewModels( for wallets: [ManagedMetaAccountModel], - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData], + balancesCalculator: BalancesCalculating, locale: Locale ) -> [WalletsListSectionViewModel] func createItemViewModel( for wallet: ManagedMetaAccountModel, - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData], + balancesCalculator: BalancesCalculating, locale: Locale ) -> WalletsListViewModel } -final class WalletsListViewModelFactory { +class WalletsListViewModelFactory { let assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol let priceAssetInfoFactory: PriceAssetInfoFactoryProtocol let currencyManager: CurrencyManagerProtocol @@ -40,122 +34,21 @@ final class WalletsListViewModelFactory { self.currencyManager = currencyManager } - func calculateValue( - chains: [ChainModel.Id: ChainModel], - balances: [ChainAssetId: BigUInt], - prices: [ChainAssetId: PriceData], - includingChainIds: Set, - excludingChainIds: Set - ) -> Decimal { - balances.reduce(Decimal.zero) { amount, chainAssetBalance in - let includingChain = includingChainIds.isEmpty || includingChainIds.contains(chainAssetBalance.key.chainId) - let excludingChain = excludingChainIds.contains(chainAssetBalance.key.chainId) - - guard - includingChain, !excludingChain, - let priceData = prices[chainAssetBalance.key], - let price = Decimal(string: priceData.price), - let asset = chains[chainAssetBalance.key.chainId]?.asset(for: chainAssetBalance.key.assetId), - let decimalBalance = Decimal.fromSubstrateAmount( - chainAssetBalance.value, - precision: Int16(bitPattern: asset.precision) - ) else { - return amount - } - - return amount + decimalBalance * price - } + func isSelected(wallet: ManagedMetaAccountModel) -> Bool { + wallet.isSelected } - func calculateTotalValue( - for wallet: ManagedMetaAccountModel, - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData] - ) -> Decimal { - let chainAccountIds = wallet.info.chainAccounts.map(\.chainId) - - var totalValue: Decimal = 0.0 - - if let substrateAccountId = wallet.info.substrateAccountId { - totalValue += calculateValue( - chains: chains, - balances: balances[substrateAccountId] ?? [:], - prices: prices, - includingChainIds: Set(), - excludingChainIds: Set(chainAccountIds) - ) - - let contributions = crowdloanContributions[substrateAccountId]? - .filter { !chainAccountIds.contains($0.key) } ?? [:] - totalValue += calculateCrowdloanContribution( - contributions, - chains: chains, - prices: prices - ) - } - - if let ethereumAddress = wallet.info.ethereumAddress { - totalValue += calculateValue( - chains: chains, - balances: balances[ethereumAddress] ?? [:], - prices: prices, - includingChainIds: Set(), - excludingChainIds: Set(chainAccountIds) - ) - - let contributions = crowdloanContributions[ethereumAddress]? - .filter { !chainAccountIds.contains($0.key) } ?? [:] - totalValue += calculateCrowdloanContribution( - contributions, - chains: chains, - prices: prices - ) - } - - wallet.info.chainAccounts.forEach { chainAccount in - totalValue += calculateValue( - chains: chains, - balances: balances[chainAccount.accountId] ?? [:], - prices: prices, - includingChainIds: [chainAccount.chainId], - excludingChainIds: Set() - ) - let contributions = crowdloanContributions[chainAccount.accountId] ?? [:] - totalValue += calculateCrowdloanContribution( - contributions, - chains: chains, - prices: prices - ) - } - - return totalValue - } - - func createSection( + private func createSection( type: WalletsListSectionViewModel.SectionType, wallets: [ManagedMetaAccountModel], - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData], + balancesCalculator: BalancesCalculating, locale: Locale ) -> WalletsListSectionViewModel? { - let viewModels = wallets - .filter { - WalletsListSectionViewModel.SectionType(walletType: $0.info.type) == type - } - .map { - createItemViewModel( - for: $0, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, - locale: locale - ) - } + let viewModels = wallets.filter { wallet in + WalletsListSectionViewModel.SectionType(walletType: wallet.info.type) == type + }.map { wallet in + createItemViewModel(for: wallet, balancesCalculator: balancesCalculator, locale: locale) + } if !viewModels.isEmpty { return WalletsListSectionViewModel(type: type, items: viewModels) @@ -163,53 +56,17 @@ final class WalletsListViewModelFactory { return nil } } - - private func calculateCrowdloanContribution( - _ contributions: [ChainModel.Id: BigUInt], - chains: [ChainModel.Id: ChainModel], - prices: [ChainAssetId: PriceData] - ) -> Decimal { - contributions.reduce(0) { result, contribution in - guard let asset = chains[contribution.key]?.utilityAsset(), - let priceData = prices[ChainAssetId(chainId: contribution.key, assetId: asset.assetId)], - let price = Decimal(string: priceData.price) else { - return result - } - guard let decimalAmount = Decimal.fromSubstrateAmount( - contribution.value, - precision: Int16(bitPattern: asset.precision) - ) else { - return result - } - - return result + decimalAmount * price - } - } } extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { func createItemViewModel( for wallet: ManagedMetaAccountModel, - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData], + balancesCalculator: BalancesCalculating, locale: Locale ) -> WalletsListViewModel { - let totalValueDecimal = calculateTotalValue( - for: wallet, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices - ) + let totalValueDecimal = balancesCalculator.calculateTotalValue(for: wallet.info) - let price = prices.first(where: { $0.value.currencyId != nil })?.value - let totalValue = formatPrice( - amount: totalValueDecimal, - priceData: price, - locale: locale - ) + let totalValue = formatPrice(amount: totalValueDecimal, locale: locale) let optIcon = wallet.info.walletIdenticonData().flatMap { try? iconGenerator.generateFromAccountId($0) } let iconViewModel = optIcon.map { DrawableIconViewModel(icon: $0) } @@ -223,16 +80,13 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { return WalletsListViewModel( identifier: wallet.identifier, walletAmountViewModel: totalAmountViewModel, - isSelected: wallet.isSelected + isSelected: isSelected(wallet: wallet) ) } func createSectionViewModels( for wallets: [ManagedMetaAccountModel], - chains: [ChainModel.Id: ChainModel], - balances: [AccountId: [ChainAssetId: BigUInt]], - crowdloanContributions: [AccountId: [ChainModel.Id: BigUInt]], - prices: [ChainAssetId: PriceData], + balancesCalculator: BalancesCalculating, locale: Locale ) -> [WalletsListSectionViewModel] { var sections: [WalletsListSectionViewModel] = [] @@ -241,10 +95,7 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { let secretsSection = createSection( type: .secrets, wallets: wallets, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, + balancesCalculator: balancesCalculator, locale: locale ) { sections.append(secretsSection) @@ -254,10 +105,7 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { let paritySignerSection = createSection( type: .paritySigner, wallets: wallets, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, + balancesCalculator: balancesCalculator, locale: locale ) { sections.append(paritySignerSection) @@ -267,10 +115,7 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { let ledgerSection = createSection( type: .ledger, wallets: wallets, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, + balancesCalculator: balancesCalculator, locale: locale ) { sections.append(ledgerSection) @@ -280,10 +125,7 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { let watchOnlySection = createSection( type: .watchOnly, wallets: wallets, - chains: chains, - balances: balances, - crowdloanContributions: crowdloanContributions, - prices: prices, + balancesCalculator: balancesCalculator, locale: locale ) { sections.append(watchOnlySection) @@ -292,8 +134,8 @@ extension WalletsListViewModelFactory: WalletsListViewModelFactoryProtocol { return sections } - func formatPrice(amount: Decimal, priceData: PriceData?, locale: Locale) -> String { - let currencyId = priceData?.currencyId ?? currencyManager.selectedCurrency.id + func formatPrice(amount: Decimal, locale: Locale) -> String { + let currencyId = currencyManager.selectedCurrency.id let assetDisplayInfo = priceAssetInfoFactory.createAssetBalanceDisplayInfo(from: currencyId) let priceFormatter = assetBalanceFormatterFactory.createTokenFormatter(for: assetDisplayInfo) return priceFormatter.value(for: locale).stringFromDecimal(amount) ?? "" diff --git a/novawallet/Modules/WalletsList/Manage/WalletManageInteractor.swift b/novawallet/Modules/WalletsList/Manage/WalletManageInteractor.swift index a61fb57bcc..88d6b2b3ae 100644 --- a/novawallet/Modules/WalletsList/Manage/WalletManageInteractor.swift +++ b/novawallet/Modules/WalletsList/Manage/WalletManageInteractor.swift @@ -18,15 +18,11 @@ final class WalletManageInteractor: WalletsListInteractor { } init( - chainRegistry: ChainRegistryProtocol, + balancesStore: BalancesStoreProtocol, walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, - priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, repository: AnyDataProviderRepository, selectedWalletSettings: SelectedWalletSettings, eventCenter: EventCenterProtocol, - currencyManager: CurrencyManagerProtocol, - crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactoryProtocol, operationQueue: OperationQueue ) { self.repository = repository @@ -35,12 +31,8 @@ final class WalletManageInteractor: WalletsListInteractor { self.eventCenter = eventCenter super.init( - chainRegistry: chainRegistry, - walletListLocalSubscriptionFactory: walletListLocalSubscriptionFactory, - walletLocalSubscriptionFactory: walletLocalSubscriptionFactory, - priceLocalSubscriptionFactory: priceLocalSubscriptionFactory, - currencyManager: currencyManager, - crowdloansLocalSubscriptionFactory: crowdloansLocalSubscriptionFactory + balancesStore: balancesStore, + walletListLocalSubscriptionFactory: walletListLocalSubscriptionFactory ) } diff --git a/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift b/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift index 951ac05398..751574357f 100644 --- a/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift +++ b/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift @@ -43,9 +43,10 @@ final class WalletManageViewFactory { } private static func createInteractor() -> WalletManageInteractor? { - guard let currencyManager = CurrencyManager.shared else { + guard let balancesStore = BalancesStore.createDefaut() else { return nil } + let facade = UserDataStorageFacade.shared let repository = AccountRepositoryFactory(storageFacade: facade).createManagedMetaAccountRepository( for: nil, @@ -53,15 +54,11 @@ final class WalletManageViewFactory { ) return WalletManageInteractor( - chainRegistry: ChainRegistryFacade.sharedRegistry, + balancesStore: balancesStore, walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactory.shared, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, - priceLocalSubscriptionFactory: PriceProviderFactory.shared, repository: repository, selectedWalletSettings: SelectedWalletSettings.shared, eventCenter: EventCenter.shared, - currencyManager: currencyManager, - crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactory.shared, operationQueue: OperationManagerFacade.sharedDefaultQueue ) } diff --git a/novawallet/Modules/WalletsList/Selection/WalletSelectionInteractor.swift b/novawallet/Modules/WalletsList/Selection/WalletSelectionInteractor.swift index b3d63f8fe9..d2f837f8df 100644 --- a/novawallet/Modules/WalletsList/Selection/WalletSelectionInteractor.swift +++ b/novawallet/Modules/WalletsList/Selection/WalletSelectionInteractor.swift @@ -15,24 +15,17 @@ final class WalletSelectionInteractor: WalletsListInteractor { let eventCenter: EventCenterProtocol init( - chainRegistry: ChainRegistryProtocol, + balancesStore: BalancesStoreProtocol, walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, - priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, settings: SelectedWalletSettings, - eventCenter: EventCenterProtocol, - currencyManager: CurrencyManagerProtocol + eventCenter: EventCenterProtocol ) { self.settings = settings self.eventCenter = eventCenter super.init( - chainRegistry: chainRegistry, - walletListLocalSubscriptionFactory: walletListLocalSubscriptionFactory, - walletLocalSubscriptionFactory: walletLocalSubscriptionFactory, - priceLocalSubscriptionFactory: priceLocalSubscriptionFactory, - currencyManager: currencyManager, - crowdloansLocalSubscriptionFactory: CrowdloanContributionLocalSubscriptionFactory.shared + balancesStore: balancesStore, + walletListLocalSubscriptionFactory: walletListLocalSubscriptionFactory ) } } diff --git a/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift b/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift index 7d594e53c3..3f982a0fa8 100644 --- a/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift +++ b/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift @@ -36,17 +36,15 @@ struct WalletSelectionViewFactory { } private static func createInteractor() -> WalletSelectionInteractor? { - guard let currencyManager = CurrencyManager.shared else { + guard let balancesStore = BalancesStore.createDefaut() else { return nil } + return WalletSelectionInteractor( - chainRegistry: ChainRegistryFacade.sharedRegistry, + balancesStore: balancesStore, walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactory.shared, - walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, - priceLocalSubscriptionFactory: PriceProviderFactory.shared, settings: SelectedWalletSettings.shared, - eventCenter: EventCenter.shared, - currencyManager: currencyManager + eventCenter: EventCenter.shared ) } } diff --git a/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift b/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift new file mode 100644 index 0000000000..d646ed54e9 --- /dev/null +++ b/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift @@ -0,0 +1,16 @@ +import XCTest + +class WalletsChooseTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + XCTFail("Did you forget to add tests?") + } +} From 471002d1a5968029b730691c0843a6d45c2157fc Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 8 May 2023 17:49:07 +0500 Subject: [PATCH 36/54] fix tests --- novawallet.xcodeproj/project.pbxproj | 24 ------------------- .../DAppWalletAuth/DAppWalletAuthTests.swift | 16 ------------- .../WalletsChoose/WalletsChooseTests.swift | 16 ------------- 3 files changed, 56 deletions(-) delete mode 100644 novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift delete mode 100644 novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 0f20ebab60..8e5df67314 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -89,7 +89,6 @@ 1F45D221E855D5340572C243 /* GovernanceUnavailableTracksProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59BED0E6DBC4C21D9625740C /* GovernanceUnavailableTracksProtocols.swift */; }; 1F496969FEE3E160BABDAC66 /* ReferendumVoteSetupProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5159EA2661A6CBE123CCF891 /* ReferendumVoteSetupProtocols.swift */; }; 1F88F3DBFA0BD6D0FDF558F3 /* SelectValidatorsConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975DECE71DE70DFD866B8E23 /* SelectValidatorsConfirmViewFactory.swift */; }; - 1FF9D36D42669B4C1122B533 /* DAppWalletAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */; }; 20B2942A4241F6713A1C70D9 /* StakingRewardDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2377F8FB07B47637346249F5 /* StakingRewardDetailsViewFactory.swift */; }; 211725E26764530359F53A38 /* ParitySignerTxQrInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859654B7C1FAC269CA61E71 /* ParitySignerTxQrInteractor.swift */; }; 21322B4A297840739C389F17 /* AccountManagementInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558BD7D1B8CA1409BE74879 /* AccountManagementInteractor.swift */; }; @@ -2912,7 +2911,6 @@ 90ACE8690DA095E4F45494E9 /* TransferConfirmProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7865BACFB8591F67D8EE06 /* TransferConfirmProtocols.swift */; }; 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA3BF0C9C1E0E2C67D962F5 /* PurchaseViewFactory.swift */; }; 91201789084DD9A419CA8CD3 /* MarkdownDescriptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F080BC55D9575EBE4216283C /* MarkdownDescriptionViewController.swift */; }; - 912812D2C46BC18F5C1C14F9 /* WalletsChooseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */; }; 912ECC319A48CAD09FB694AC /* AssetsSearchProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9F9E43E296386A7F138326 /* AssetsSearchProtocols.swift */; }; 91530F7301CA39654E008580 /* DAppBrowserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A4DFCC4236D25A3D59F809 /* DAppBrowserInteractor.swift */; }; 91A1286763617DE022BD495F /* LedgerInstructionsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B08ACC71BE679A48A7B66E /* LedgerInstructionsPresenter.swift */; }; @@ -3567,7 +3565,6 @@ 1DECC58C93DB18E79A03B5A0 /* AssetSelectionProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssetSelectionProtocols.swift; sourceTree = ""; }; 1E067006C1BC9DFCA5E8DB86 /* GovernanceUnlockConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GovernanceUnlockConfirmWireframe.swift; sourceTree = ""; }; 1E5CB64B91B35804B3671456 /* ControllerAccountPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountPresenter.swift; sourceTree = ""; }; - 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppWalletAuthTests.swift; sourceTree = ""; }; 1F3A05E0F46351784030D1AA /* ChainAddressDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChainAddressDetailsPresenter.swift; sourceTree = ""; }; 1F7865BACFB8591F67D8EE06 /* TransferConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransferConfirmProtocols.swift; sourceTree = ""; }; 1FF860B3465854DCBC02DFB3 /* DAppBrowserPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserPresenter.swift; sourceTree = ""; }; @@ -6969,7 +6966,6 @@ FCA43AABAC7555C2E648442F /* DAppSettingsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSettingsViewFactory.swift; sourceTree = ""; }; FE0641B2354E6F236CB9A132 /* NftDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftDetailsPresenter.swift; sourceTree = ""; }; FE4AF0849E32E5B9C72E2ABB /* RecommendedValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewFactory.swift; sourceTree = ""; }; - FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletsChooseTests.swift; sourceTree = ""; }; FF4688AF0658F8BB7A90C2BE /* ExportMnemonicConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicConfirmViewFactory.swift; sourceTree = ""; }; FF755EE09598254BB5E59CC2 /* ReferendumVoteSetupViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumVoteSetupViewFactory.swift; sourceTree = ""; }; FFB4A14C99D151B41F61F474 /* DAppTxDetailsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppTxDetailsInteractor.swift; sourceTree = ""; }; @@ -7457,14 +7453,6 @@ path = Confirm; sourceTree = ""; }; - 312F85F94736E09922F5733F /* DAppWalletAuth */ = { - isa = PBXGroup; - children = ( - 1EC0C81094C80A323CF659F1 /* DAppWalletAuthTests.swift */, - ); - path = DAppWalletAuth; - sourceTree = ""; - }; 33419BD574C1318B6848B556 /* DAppOperationConfirm */ = { isa = PBXGroup; children = ( @@ -12418,8 +12406,6 @@ 84B7C705289BFA79001A3566 /* AccountManagement */, 84B7C708289BFA79001A3566 /* WalletList */, 84B7C70A289BFA79001A3566 /* ControllerAccount */, - 312F85F94736E09922F5733F /* DAppWalletAuth */, - BB3F7FDC375F90A4BE705D5E /* WalletsChoose */, ); path = Modules; sourceTree = ""; @@ -15842,14 +15828,6 @@ path = StakingRewardPayouts; sourceTree = ""; }; - BB3F7FDC375F90A4BE705D5E /* WalletsChoose */ = { - isa = PBXGroup; - children = ( - FF32AE421EF57AF58F7F0B1A /* WalletsChooseTests.swift */, - ); - path = WalletsChoose; - sourceTree = ""; - }; BF6F50DD15230CADAC713359 /* AccountImport */ = { isa = PBXGroup; children = ( @@ -20447,8 +20425,6 @@ 84B7C748289BFA79001A3566 /* WalletListTests.swift in Sources */, 84B7C720289BFA79001A3566 /* ReferralCrowdloanTests.swift in Sources */, F4897BB126AED13D0075F291 /* EraCountdownOperationFactoryStub.swift in Sources */, - 1FF9D36D42669B4C1122B533 /* DAppWalletAuthTests.swift in Sources */, - 912812D2C46BC18F5C1C14F9 /* WalletsChooseTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift b/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift deleted file mode 100644 index 8c4d72bd6a..0000000000 --- a/novawalletTests/Modules/DAppWalletAuth/DAppWalletAuthTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -import XCTest - -class DAppWalletAuthTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - XCTFail("Did you forget to add tests?") - } -} diff --git a/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift b/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift deleted file mode 100644 index d646ed54e9..0000000000 --- a/novawalletTests/Modules/WalletsChoose/WalletsChooseTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -import XCTest - -class WalletsChooseTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - XCTFail("Did you forget to add tests?") - } -} From b5708164d9b80b03d7c115f9fc66e753c6ee3dbf Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 9 May 2023 10:01:40 +0500 Subject: [PATCH 37/54] networks presentation --- .../Protocols/WalletChoosePresentable.swift | 2 +- .../ModalPicker/ModalNetworksFactory.swift | 142 ++++++++++++++++-- .../ModalPickerViewController.swift | 40 ++++- .../DAppWalletAuthPresenter.swift | 8 +- .../DAppWalletAuthProtocols.swift | 5 + .../DAppWalletAuthWireframe.swift | 19 +-- .../DApp/Model/DAppChainsResolution.swift | 2 + novawallet/en.lproj/Localizable.strings | 2 + novawallet/en.lproj/Localizable.stringsdict | 16 ++ novawallet/ru.lproj/Localizable.strings | 2 + novawallet/ru.lproj/Localizable.stringsdict | 20 +++ 11 files changed, 233 insertions(+), 25 deletions(-) diff --git a/novawallet/Common/Protocols/WalletChoosePresentable.swift b/novawallet/Common/Protocols/WalletChoosePresentable.swift index 6391a3caa4..ec98f406d7 100644 --- a/novawallet/Common/Protocols/WalletChoosePresentable.swift +++ b/novawallet/Common/Protocols/WalletChoosePresentable.swift @@ -2,7 +2,7 @@ import Foundation protocol WalletChoosePresentable: AnyObject { func showWalletChoose( - from view: DAppWalletAuthViewProtocol?, + from view: ControllerBackedProtocol?, selectedWalletId: String, delegate: WalletsChooseDelegate ) diff --git a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift index 65abbfc0ec..1c325260c5 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift +++ b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift @@ -13,9 +13,6 @@ enum ModalNetworksFactory { viewController.modalPresentationStyle = .custom viewController.separatorStyle = .none - viewController.cellHeight = 52.0 - viewController.headerHeight = 40.0 - viewController.footerHeight = 0.0 viewController.headerBorderType = [] viewController.actionType = .none @@ -23,6 +20,21 @@ enum ModalNetworksFactory { return viewController } + private static func createDAppsNetworksController( + for title: LocalizableResource + ) -> ModalPickerViewController { + let viewController: ModalPickerViewController + = createNetworksController(for: title) + + viewController.cellHeight = 48.0 + viewController.headerHeight = 40.0 + viewController.footerHeight = 0.0 + viewController.sectionHeaderHeight = 32 + viewController.sectionFooterHeight = 32 + + return viewController + } + static func createNetworkSelectionList( selectionState: CrossChainDestinationSelectionState, delegate: ModalPickerViewControllerDelegate?, @@ -96,26 +108,136 @@ enum ModalNetworksFactory { ) } - viewController = createNetworksController(for: title) + viewController = createDAppsNetworksController(for: title) - let networkViewModelFactory = NetworkViewModelFactory() + viewController.viewModels = convertNetworkListToViewModels(from: networks) - let viewModels = networks.map { network in - LocalizableResource { _ in - networkViewModelFactory.createViewModel(from: network) + let factory = ModalSheetPresentationFactory(configuration: .nova) + viewController.modalTransitioningFactory = factory + + let height = viewController.headerHeight + CGFloat(networks.count) * viewController.cellHeight + viewController.preferredContentSize = CGSize(width: 0.0, height: height) + + viewController.localizationManager = LocalizationManager.shared + + return viewController + } + + static func createResolutionInfoList( + for required: DAppChainsResolution, + optional: DAppChainsResolution? + ) -> UIViewController? { + let viewController: ModalPickerViewController + + let networksCount = required.totalChainsCount + (optional?.totalChainsCount ?? 0) + + let title = LocalizableResource { locale in + R.string.localizable.commonNetworksTitle( + networksCount, + preferredLanguages: locale.rLanguages + ) + } + + viewController = createDAppsNetworksController(for: title) + + let rowsCount = required.resolved.count + (optional?.resolved.count ?? 0) + var sectionsCount: Int = 0 + var footersCount: Int = 0 + + if required.hasChains { + let hasFooter = addNetworksSection( + to: viewController, + from: required, + title: LocalizableResource { locale in + R.string.localizable.dappsRequiredNetworks(preferredLanguages: locale.rLanguages) + } + ) + + sectionsCount += 1 + + if hasFooter { + footersCount += 1 } } - viewController.viewModels = viewModels + if let optional = optional, optional.hasChains { + let hasFooter = addNetworksSection( + to: viewController, + from: optional, + title: LocalizableResource { locale in + R.string.localizable.dappsOptionalNetworks(preferredLanguages: locale.rLanguages) + } + ) + + sectionsCount += 1 + + if hasFooter { + footersCount += 1 + } + } let factory = ModalSheetPresentationFactory(configuration: .nova) viewController.modalTransitioningFactory = factory - let height = viewController.headerHeight + CGFloat(networks.count) * viewController.cellHeight + let height = viewController.headerHeight + + CGFloat(rowsCount) * viewController.cellHeight + + CGFloat(sectionsCount) * viewController.sectionHeaderHeight + + CGFloat(footersCount) * viewController.sectionFooterHeight viewController.preferredContentSize = CGSize(width: 0.0, height: height) viewController.localizationManager = LocalizationManager.shared return viewController } + + private static func addNetworksSection( + to viewController: ModalPickerViewController, + from resolution: DAppChainsResolution, + title: LocalizableResource + ) -> Bool { + let requiredViewModels = convertNetworkSetToViewModels(from: resolution.resolved) + + let sectionFooter: LocalizableResource? + + if resolution.hasUnresolved { + sectionFooter = LocalizableResource { locale in + R.string.localizable.dappsUnsupportedNetworksFormat( + format: resolution.unresolved.count, + preferredLanguages: locale.rLanguages + ) + } + } else { + sectionFooter = nil + } + + viewController.addSection( + viewModels: requiredViewModels, + title: title, + footer: sectionFooter + ) + + return sectionFooter != nil + } + + private static func convertNetworkListToViewModels( + from networkList: [ChainModel] + ) -> [LocalizableResource] { + let networkViewModelFactory = NetworkViewModelFactory() + + return networkList.map { network in + LocalizableResource { _ in + networkViewModelFactory.createViewModel(from: network) + } + } + } + + private static func convertNetworkSetToViewModels( + from networkSet: Set + ) -> [LocalizableResource] { + let networkList = networkSet.sorted(by: { chain1, chain2 in + ChainModelCompator.defaultComparator(chain1: chain1, chain2: chain2) + }) + + return convertNetworkListToViewModels(from: networkList) + } } diff --git a/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.swift b/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.swift index ba80f2c7c3..734af0bee5 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.swift +++ b/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.swift @@ -51,6 +51,7 @@ class ModalPickerViewController var selectedIndex: Int = 0 var selectedSection: Int = 0 var sectionHeaderHeight: CGFloat = 26.0 + var sectionFooterHeight: CGFloat = 26.0 var isScrollEnabled: Bool = false var hasCloseItem: Bool = false @@ -60,6 +61,7 @@ class ModalPickerViewController private var sections: [[LocalizableResource]] = [] private var sectionTitles: [Int: LocalizableResource] = [:] + private var sectionFooters: [Int: LocalizableResource] = [:] var viewModels: [LocalizableResource] { get { @@ -88,10 +90,20 @@ class ModalPickerViewController } func addSection(viewModels: [LocalizableResource], title: LocalizableResource?) { + addSection(viewModels: viewModels, title: title, footer: nil) + } + + func addSection( + viewModels: [LocalizableResource], + title: LocalizableResource?, + footer: LocalizableResource? + ) { sections.append(viewModels) let lastSectionIndex = sections.count - 1 + sectionTitles[lastSectionIndex] = title + sectionFooters[lastSectionIndex] = footer } private func configure() { @@ -224,8 +236,7 @@ class ModalPickerViewController if let title = sectionTitles[itemSectionIndex] { let headerView: IconTitleHeaderView = tableView.dequeueReusableHeaderFooterView() - headerView.titleView.detailsLabel.textColor = R.color.colorTextSecondary() - headerView.titleView.detailsLabel.font = .regularFootnote + headerView.titleView.detailsLabel.apply(style: .footnoteSecondary) headerView.bind(title: title.value(for: selectedLocale), icon: nil) @@ -235,6 +246,31 @@ class ModalPickerViewController } } + func tableView(_: UITableView, heightForFooterInSection section: Int) -> CGFloat { + let itemSectionIndex = actionType.hasAction ? section - 1 : section + + if sectionFooters[itemSectionIndex] != nil { + return sectionFooterHeight + } else { + return 0.0 + } + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + let itemSectionIndex = actionType.hasAction ? section - 1 : section + + if let footer = sectionFooters[itemSectionIndex] { + let footerView: IconTitleHeaderView = tableView.dequeueReusableHeaderFooterView() + footerView.titleView.detailsLabel.apply(style: .footnoteSecondary) + + footerView.bind(title: footer.value(for: selectedLocale), icon: nil) + + return footerView + } else { + return nil + } + } + // MARK: Table View Data Source func numberOfSections(in _: UITableView) -> Int { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift index 2c424df504..49eff00840 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthPresenter.swift @@ -78,7 +78,13 @@ extension DAppWalletAuthPresenter: DAppWalletAuthPresenterProtocol { ) } - func showNetworks() {} + func showNetworks() { + wireframe.showNetworksResolution( + from: view, + requiredResolution: request.requiredChains, + optionalResolution: request.optionalChains + ) + } } extension DAppWalletAuthPresenter: DAppWalletAuthInteractorOutputProtocol { diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift index f5f7ef60f0..538ed69a0d 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthProtocols.swift @@ -24,4 +24,9 @@ protocol DAppWalletAuthInteractorOutputProtocol: AnyObject { protocol DAppWalletAuthWireframeProtocol: WalletChoosePresentable { func close(from view: DAppWalletAuthViewProtocol?) + func showNetworksResolution( + from view: DAppWalletAuthViewProtocol?, + requiredResolution: DAppChainsResolution, + optionalResolution: DAppChainsResolution? + ) } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift index bc688a65e3..805df53f4e 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift @@ -5,21 +5,18 @@ final class DAppWalletAuthWireframe: DAppWalletAuthWireframeProtocol { view?.controller.presentingViewController?.dismiss(animated: true, completion: nil) } - func showWalletChoose( + func showNetworksResolution( from view: DAppWalletAuthViewProtocol?, - selectedWalletId: String, - delegate: WalletsChooseDelegate + requiredResolution: DAppChainsResolution, + optionalResolution: DAppChainsResolution? ) { - guard - let chooseView = WalletsChooseViewFactory.createView( - for: selectedWalletId, - delegate: delegate - ) else { + guard let networksView = ModalNetworksFactory.createResolutionInfoList( + for: requiredResolution, + optional: optionalResolution + ) else { return } - let navigationController = NovaNavigationController(rootViewController: chooseView.controller) - - view?.controller.present(navigationController, animated: true) + view?.controller.present(networksView, animated: true) } } diff --git a/novawallet/Modules/DApp/Model/DAppChainsResolution.swift b/novawallet/Modules/DApp/Model/DAppChainsResolution.swift index dd3a8c20fb..81a2ff40e3 100644 --- a/novawallet/Modules/DApp/Model/DAppChainsResolution.swift +++ b/novawallet/Modules/DApp/Model/DAppChainsResolution.swift @@ -9,6 +9,8 @@ struct DAppChainsResolution { var hasChains: Bool { hasResolved || hasUnresolved } + var totalChainsCount: Int { resolved.count + unresolved.count } + init(resolved: Set = [], unresolved: Set = []) { self.resolved = resolved self.unresolved = unresolved diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index 2ab2e2d468..2b8c31c9db 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1219,3 +1219,5 @@ "common.status" = "Status"; "common.none" = "None"; "dapps.missing.required.networks.warning.format" = "Some of the required networks requested by “%@” are not supported in Nova Wallet"; +"dapps.required.networks" = "Required"; +"dapps.optional.networks" = "Optional"; diff --git a/novawallet/en.lproj/Localizable.stringsdict b/novawallet/en.lproj/Localizable.stringsdict index 95836ad6b1..651f08a61c 100644 --- a/novawallet/en.lproj/Localizable.stringsdict +++ b/novawallet/en.lproj/Localizable.stringsdict @@ -162,5 +162,21 @@ %2$@ accounts are missing. Add accounts to the wallet in Settings + dapps.unsupported.networks.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 unsupported network hidden + other + %li unsupported networks hidden + + diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index b967f2121e..99b44dd896 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1219,3 +1219,5 @@ "common.status" = "Статус"; "common.none" = "Отсутствуют"; "dapps.missing.required.networks.warning.format" = "Некоторые запрошенные “%@” сети не поддерживаются в Nova Wallet"; +"dapps.required.networks" = "Обязательные"; +"dapps.optional.networks" = "Не обязательные"; diff --git a/novawallet/ru.lproj/Localizable.stringsdict b/novawallet/ru.lproj/Localizable.stringsdict index 78a8b35380..89495f504e 100644 --- a/novawallet/ru.lproj/Localizable.stringsdict +++ b/novawallet/ru.lproj/Localizable.stringsdict @@ -190,5 +190,25 @@ %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках + dapps.unsupported.networks.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 не поддерживаемая сеть скрыта + few + %li не поддерживаемые сети скрыты + many + %li не поддерживаемых сетей скрыто + other + %li не поддерживаемых сетей скрыто + + From a80da2f7cd251a1eb667c2636628f231930a7a2b Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 9 May 2023 16:23:50 +0500 Subject: [PATCH 38/54] fix footer --- .../Common/View/TableHeader/IconTitleHeaderView.swift | 3 ++- .../ModalPicker/ModalNetworksFactory.swift | 2 +- .../ModalPicker/ModalPickerViewController.xib | 11 ++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/novawallet/Common/View/TableHeader/IconTitleHeaderView.swift b/novawallet/Common/View/TableHeader/IconTitleHeaderView.swift index 223901f8eb..62b25cfd26 100644 --- a/novawallet/Common/View/TableHeader/IconTitleHeaderView.swift +++ b/novawallet/Common/View/TableHeader/IconTitleHeaderView.swift @@ -26,7 +26,8 @@ final class IconTitleHeaderView: UITableViewHeaderFooterView { override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) - backgroundColor = .clear + backgroundView = UIView() + backgroundView?.backgroundColor = .clear setupLayout() } diff --git a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift index 1c325260c5..2c31975320 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift +++ b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift @@ -27,7 +27,7 @@ enum ModalNetworksFactory { = createNetworksController(for: title) viewController.cellHeight = 48.0 - viewController.headerHeight = 40.0 + viewController.headerHeight = 32.0 viewController.footerHeight = 0.0 viewController.sectionHeaderHeight = 32 viewController.sectionFooterHeight = 32 diff --git a/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.xib b/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.xib index 669dc56e53..0605bf2129 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.xib +++ b/novawallet/Common/ViewController/ModalPicker/ModalPickerViewController.xib @@ -1,9 +1,9 @@ - + - + @@ -26,7 +26,7 @@ - + @@ -98,6 +98,11 @@ + + + + + From 689729f5ff654bddb2dc433db055d92153fe6e20 Mon Sep 17 00:00:00 2001 From: ERussel Date: Tue, 9 May 2023 16:41:36 +0500 Subject: [PATCH 39/54] fix icons --- .../iconDappExtension.imageset/Contents.json | 2 +- .../{nova-logo.pdf => iconDappExtension.pdf} | Bin 883784 -> 883794 bytes .../DAppAuthConfirmViewController.swift | 4 ++-- .../DAppAuthConfirmViewLayout.swift | 13 ++++++++----- 4 files changed, 11 insertions(+), 8 deletions(-) rename novawallet/Assets.xcassets/iconDappExtension.imageset/{nova-logo.pdf => iconDappExtension.pdf} (99%) diff --git a/novawallet/Assets.xcassets/iconDappExtension.imageset/Contents.json b/novawallet/Assets.xcassets/iconDappExtension.imageset/Contents.json index 306303fbd2..c003657f1a 100644 --- a/novawallet/Assets.xcassets/iconDappExtension.imageset/Contents.json +++ b/novawallet/Assets.xcassets/iconDappExtension.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "nova-logo.pdf", + "filename" : "iconDappExtension.pdf", "idiom" : "universal" } ], diff --git a/novawallet/Assets.xcassets/iconDappExtension.imageset/nova-logo.pdf b/novawallet/Assets.xcassets/iconDappExtension.imageset/iconDappExtension.pdf similarity index 99% rename from novawallet/Assets.xcassets/iconDappExtension.imageset/nova-logo.pdf rename to novawallet/Assets.xcassets/iconDappExtension.imageset/iconDappExtension.pdf index a9061ea628d7fae8ce797c695c5cd0ca90be261e..a305fec7d439baf06b0052a05f6d2f1553969f2a 100644 GIT binary patch delta 1964 zcmZ`(z0Msq5Y-n52~KWB9>7hc2nowGo*CQpp`{B&%G)4ui3Adn5{jkRceoD%60bm= zI!}-%NSWjPvGwu%j17_l0%A-jSo@fkdkUj$Cw2wR5M}{qC`r?pPkmArm3no)R1wE zhtr|ktL0tLHWHL?RdWeBN6yt>4tG)sR)M%IG^Uj?rC=sv`M{%w zLIvgR&Oswc5{v407V1%2QUS${1H_4|VVdD7Itoh*Ow=Jy+1)bL;n-eZS%CzT2)Pzjw-hk41k6paJ~sI;27IvIoge zM==&D$PdXjF!dnq*)8aEMR3?`MURnggOVM7-3Ka$VeIhN*|&Dnz>iP+25bb%(d+1Z z$F5x5&+aGp%Khd{Lv0U_DRl1to4Xgw_1zz*@|dXEgGD^i^~vkgx07I_md7*iCv4`3 zQV`|{j29;{NK~@qIfjDOgZC1{=#~@=Arxaz&4X8sYJ^Fa5V7^#_LXzMUYD$%+KFpn z&l4<>O(Fo{ggayL42qJ)5Ys@Ck~LLXkk7TC5rveQNj)dzEn>?sE)rq%=Ab!OENPbo zwHt|pbuX@QkVz=VRvpFC%7HjCFHMYvv1Z_)tb*(TPUFA{`?mnEN)a`nAVyf`mNm{9 zSq-sDs<*Xh6k;;v=^$GQTBRg#=<0S3JA(uzMsVY*n9V5OG_i%k(!@9IGX67w*x@vl zIZp-jVcCwH+bKs(aWHI?gU;~W&;m0qYL?pHr%irUt+}Dmrp`e;T$74w7Qd5F-~BbU z$@Y=;FbK8Tsunb87gS9EIy}%CrHn}*+;W7}Xso_!;G>iZS#eqo*s{$+(NDvxt01c$ z!_`B&gZsP01Mp5lLFiU=+b(R+*r~p0m+RkePS0-2_5GXEi*LrCp`ZSJ{`U0C&G$xr kUD8*sy-P_{-gstq*59{Z@$0YnaXy*P&FAL}^Tqkne0e^dugt@Ib-woL@c8w_ z$@!$Dehk@p{81cV3=&+xGUpvC*jHw+J1lsYV^P!ubuk518lMb7oUrGNlhIZ=mvxn7 zth?gLY9=cRI9NXdt}v7v8Zm4|OM;tRoMjx@r|3vEQW{xYLMRm`21g|+qQK-l zGb=(Nh_;Ao&6t-$78Q0$W5!(3R2;_`ae&B`T!gTlT~3%}Hq4VqOem-1Ff}32m>nmd zJhs(kkzhAE#T$?oD6khVYnyGTz;@BF1{q?JZb*n_63o6;Q;e{{B>1RocPQ@7ivmV+ zvAHJ$1tA21YS1nkBrPlT*-l~?lgp^ayO2_n`kmDYd>lbEFihPurxXIihi+|e>&oA3 z*g(bTLn&EfdU11PF{#~QzOf**J2G$>o(qo?+<}6|8_keF7;jTh^&z63yk?Z6m(>Z2 zZB|7qP~9!FBtSI$Y!cvvB_@$nS*-_KwopAa6*6uxdIK53t4F--136f4djIVrQHb9q z7?$+&+-yPlvgKldDUg2$W`aGTe2Yog? zJ$`w2xOtL}Ki(Z4Uf+IC`V;Z_!TZDWle>5(?OFEY%fbHTEQBev Date: Tue, 9 May 2023 17:58:02 +0500 Subject: [PATCH 40/54] new confirm screen style --- .../View/StackTable/StackActionView.swift | 10 +- .../Transports/DAppMetamaskTransport.swift | 2 +- .../DAppOperationConfirmViewController.swift | 71 +++--- .../DAppOperationConfirmViewLayout.swift | 218 ++++++------------ .../DAppOperationConfirmViewModel.swift | 1 + ...DAppOperationConfirmViewModelFactory.swift | 1 + .../States/WalletConnectStateNewMessage.swift | 2 +- 7 files changed, 107 insertions(+), 198 deletions(-) diff --git a/novawallet/Common/View/StackTable/StackActionView.swift b/novawallet/Common/View/StackTable/StackActionView.swift index 5b09e23ded..78cd458564 100644 --- a/novawallet/Common/View/StackTable/StackActionView.swift +++ b/novawallet/Common/View/StackTable/StackActionView.swift @@ -21,6 +21,12 @@ final class StackActionView: UIView { iconImageView.snp.updateConstraints { make in make.height.equalTo(iconSize) } + + let iconOffset = iconSize > 0 ? 12 : 0 + + titleLabel.snp.updateConstraints { make in + make.leading.equalTo(iconImageView.snp.trailing).offset(iconOffset) + } } } @@ -52,8 +58,10 @@ final class StackActionView: UIView { addSubview(view) + let iconOffset: CGFloat = iconSize > 0 ? 12 : 0 + titleLabel.snp.remakeConstraints { make in - make.leading.equalTo(iconImageView.snp.trailing).offset(12.0) + make.leading.equalTo(iconImageView.snp.trailing).offset(iconOffset) make.centerY.equalToSuperview() } diff --git a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift index 1f3bb135f5..a0a24e186a 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Transports/DAppMetamaskTransport.swift @@ -29,7 +29,7 @@ final class DAppMetamaskTransport { identifier: "\(messageId)", wallet: dataSource.wallet, accountId: accountId, - dApp: dataSource.dApp?.name ?? "", + dApp: dataSource.dApp?.url.absoluteString ?? "", dAppIcon: dataSource.dApp?.icon, operationData: signingOperation ) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift index f9b03133eb..6e8aa5f5c0 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift @@ -38,26 +38,27 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { private func setupHandlers() { rootView.confirmButton.addTarget(self, action: #selector(actionConfirm), for: .touchUpInside) rootView.rejectButton.addTarget(self, action: #selector(actionReject), for: .touchUpInside) - rootView.transactionDetailsControl.addTarget(self, action: #selector(actionTxDetails), for: .touchUpInside) + rootView.transactionDetailsCell.addTarget(self, action: #selector(actionTxDetails), for: .touchUpInside) } private func setupLocalization() { let languages = selectedLocale.rLanguages rootView.titleLabel.text = R.string.localizable.commonConfirmTitle(preferredLanguages: languages) rootView.subtitleLabel.text = R.string.localizable.dappConfirmSubtitle(preferredLanguages: languages) - rootView.walletView.rowContentView.titleView.text = R.string.localizable.commonWallet( + rootView.dAppCell.titleLabel.text = R.string.localizable.commonDapp(preferredLanguages: languages) + rootView.walletCell.titleLabel.text = R.string.localizable.commonWallet( preferredLanguages: languages ) - rootView.accountAddressView.rowContentView.titleView.text = R.string.localizable.commonAccountAddress( + rootView.accountCell.titleLabel.text = R.string.localizable.commonAccountAddress( preferredLanguages: languages ) - rootView.networkView.rowContentView.titleView.text = R.string.localizable.commonNetwork( + rootView.networkCell.titleLabel.text = R.string.localizable.commonNetwork( preferredLanguages: languages ) - rootView.networkFeeView.titleLabel.text = R.string.localizable.commonNetworkFee( - preferredLanguages: languages - ) - rootView.transactionDetailsControl.rowContentView.titleView.text = R.string.localizable.commonTxDetails( + + rootView.feeCell.rowContentView.locale = selectedLocale + + rootView.transactionDetailsCell.titleLabel.text = R.string.localizable.commonTxDetails( preferredLanguages: languages ) @@ -84,55 +85,39 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { extension DAppOperationConfirmViewController: DAppOperationConfirmViewProtocol { func didReceive(confimationViewModel: DAppOperationConfirmViewModel) { - let networkImageView = rootView.networkView.rowContentView.valueView.imageView - viewModel?.networkIconViewModel?.cancel(on: networkImageView) - - viewModel = confimationViewModel + rootView.dAppCell.bind(details: confimationViewModel.dApp) rootView.iconView.bind( viewModel: viewModel?.iconImageViewModel, - size: DAppOperationConfirmViewLayout.titleImageSize - ) - - rootView.walletView.rowContentView.valueView.detailsLabel.text = confimationViewModel.walletName - - let walletImageView = rootView.walletView.rowContentView.valueView.imageView - walletImageView.image = confimationViewModel.walletIcon?.imageWithFillColor( - R.color.colorIconPrimary()!, - size: DAppOperationConfirmViewLayout.listImageSize, - contentScale: UIScreen.main.scale - ) - - rootView.accountAddressView.rowContentView.valueView.detailsLabel.text = confimationViewModel.address - - let addressImageView = rootView.accountAddressView.rowContentView.valueView.imageView - addressImageView.image = confimationViewModel.addressIcon?.imageWithFillColor( - R.color.colorIconPrimary()!, - size: DAppOperationConfirmViewLayout.listImageSize, - contentScale: UIScreen.main.scale + size: DAppIconLargeConstants.displaySize ) - rootView.networkView.rowContentView.valueView.detailsLabel.text = confimationViewModel.networkName + rootView.walletCell.bind(viewModel: .init( + details: confimationViewModel.walletName, + imageViewModel: confimationViewModel.walletIcon.map({ DrawableIconViewModel(icon: $0) }) + )) - networkImageView.image = nil + rootView.accountCell.bind(viewModel: .init( + details: confimationViewModel.address, + imageViewModel: confimationViewModel.addressIcon.map({ DrawableIconViewModel(icon: $0) }) + )) - confimationViewModel.networkIconViewModel?.loadImage( - on: networkImageView, - targetSize: DAppOperationConfirmViewLayout.listImageSize, - animated: true - ) + rootView.networkCell.bind(viewModel: .init( + details: confimationViewModel.networkName, + imageViewModel: confimationViewModel.networkIconViewModel + )) } func didReceive(feeViewModel: DAppOperationFeeViewModel) { switch feeViewModel { case .loading: - rootView.networkFeeView.isHidden = false - rootView.networkFeeView.bind(viewModel: nil) + rootView.feeCell.isHidden = false + rootView.feeCell.rowContentView.bind(viewModel: nil) case .empty: - rootView.networkFeeView.isHidden = true + rootView.feeCell.isHidden = true case let .loaded(value): - rootView.networkFeeView.isHidden = false - rootView.networkFeeView.bind(viewModel: value) + rootView.feeCell.isHidden = false + rootView.feeCell.rowContentView.bind(viewModel: value) } } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift index 6b5c5c9557..10c3db9642 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift @@ -1,185 +1,99 @@ import UIKit import SoraUI -final class DAppOperationConfirmViewLayout: UIView, AdaptiveDesignable { - static let titleImageSize = CGSize(width: 56, height: 56) +final class DAppOperationConfirmViewLayout: SCGenericActionLayoutView { static let listImageSize = CGSize(width: 24, height: 24) - let iconView: DAppIconView = { - let view = DAppIconView() - return view - }() - - let titleLabel: UILabel = { - let label = UILabel() - label.textColor = R.color.colorTextPrimary() - label.font = .semiBoldTitle3 - label.textAlignment = .center - return label - }() - - let subtitleLabel: UILabel = { - let label = UILabel() - label.textColor = R.color.colorTextSecondary() - label.font = .regularFootnote - label.numberOfLines = 3 - label.textAlignment = .center - return label - }() - - let walletView = createIconDetailsRowView() - - let accountAddressView = createIconDetailsRowView() - - let networkView = createIconDetailsRowView() - - let networkFeeView: NetworkFeeView = { - let view = NetworkFeeView() - view.titleLabel.textColor = R.color.colorTextSecondary() - view.titleLabel.font = .regularFootnote - view.tokenLabel.textColor = R.color.colorTextPrimary() - view.tokenLabel.font = .regularFootnote - view.borderView.strokeWidth = 0.5 - view.borderView.strokeColor = R.color.colorContainerBorder()! - return view - }() - - let transactionDetailsControl: RowView> = { - let titleLabel = UILabel() - titleLabel.textColor = R.color.colorTextSecondary() - titleLabel.font = .regularFootnote - - let arrowImageView = UIImageView() - arrowImageView.image = R.image.iconSmallArrow()?.withRenderingMode(.alwaysTemplate) - arrowImageView.tintColor = R.color.colorIconPrimary() - - let titleView = GenericTitleValueView(titleView: titleLabel, valueView: arrowImageView) - - let rowView = RowView(contentView: titleView, preferredHeight: 48.0) - rowView.borderView.strokeWidth = 0.0 - rowView.contentInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - - return rowView - }() - - let stackView: UIStackView = { - let view = UIStackView() - view.axis = .vertical - view.alignment = .fill - return view - }() - - let rejectButton: TriangularedButton = { - let button = TriangularedButton() - button.applySecondaryDefaultStyle() - return button - }() + let iconView: DAppIconView = .create { view in + view.contentInsets = DAppIconLargeConstants.insets + } - let confirmButton: TriangularedButton = { - let button = TriangularedButton() - button.applyDefaultStyle() - return button - }() + let titleLabel: UILabel = .create { view in + view.apply(style: .title3Primary) + view.textAlignment = .center + } + + let subtitleLabel: UILabel = .create { view in + view.apply(style: .footnoteSecondary) + view.numberOfLines = 3 + view.textAlignment = .center + } + + let dAppTableView: StackTableView = .create { view in + view.cellHeight = 52 + } + + let dAppCell = StackTableCell() - override init(frame: CGRect) { - super.init(frame: frame) + let senderTableView = StackTableView() - backgroundColor = R.color.colorBottomSheetBackground() + let walletCell = StackTableCell() - setupLayout() + let accountCell: StackInfoTableCell = .create { + $0.detailsLabel.lineBreakMode = .byTruncatingMiddle } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") + let networkCell = StackTableCell() + + let feeCell = StackNetworkFeeCell() + + let transactionDetailsTableView: StackTableView = .create { view in + view.cellHeight = 52 + } + + let transactionDetailsCell: StackActionCell = .create { view in + view.rowContentView.iconSize = 0 } - // swiftlint:disable:next function_body_length - private func setupLayout() { - addSubview(iconView) + let rejectButton: TriangularedButton = .create { button in + button.applySecondaryDefaultStyle() + } + + let confirmButton: TriangularedButton = .create { button in + button.applyDefaultStyle() + } + + override func setupLayout() { + super.setupLayout() + + genericActionView.axis = .horizontal + genericActionView.distribution = .fillEqually + genericActionView.spacing = 16 + + genericActionView.addArrangedSubview(rejectButton) + genericActionView.addArrangedSubview(confirmButton) + + let headerView = UIView() + + headerView.addSubview(iconView) iconView.snp.makeConstraints { make in make.top.equalToSuperview() make.centerX.equalToSuperview() make.size.equalTo(88.0) } - addSubview(titleLabel) + headerView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16.0) make.top.equalTo(iconView.snp.bottom).offset(20.0) } - addSubview(subtitleLabel) + headerView.addSubview(subtitleLabel) subtitleLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16.0) make.top.equalTo(titleLabel.snp.bottom).offset(12.0) + make.bottom.equalToSuperview() } - addSubview(stackView) - stackView.snp.makeConstraints { make in - make.top.equalTo(subtitleLabel.snp.bottom).offset(20.0) - make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) - } - - let stackViews: [UIView] = [ - walletView, - accountAddressView, - networkView, - networkFeeView - ] - - for view in stackViews { - stackView.addArrangedSubview(view) - view.snp.makeConstraints { make in - make.height.equalTo(48.0) - } - } - - addSubview(transactionDetailsControl) - transactionDetailsControl.snp.makeConstraints { make in - make.top.equalTo(stackView.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(48.0) - } - - addSubview(rejectButton) - rejectButton.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(UIConstants.horizontalInset) - make.trailing.equalTo(self.snp.centerX).offset(-8.0) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-16.0) - make.height.equalTo(UIConstants.actionHeight) - } + addArrangedSubview(headerView, spacingAfter: 24) - addSubview(confirmButton) - confirmButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(UIConstants.horizontalInset) - make.leading.equalTo(self.snp.centerX).offset(8.0) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-16.0) - make.height.equalTo(UIConstants.actionHeight) - } - } -} + addArrangedSubview(dAppTableView, spacingAfter: 8) + dAppTableView.addArrangedSubview(dAppCell) -extension DAppOperationConfirmViewLayout { - private static func createIconDetailsRowView() -> RowView> { - let titleLabel = UILabel() - titleLabel.textColor = R.color.colorTextSecondary() - titleLabel.font = .regularFootnote - - let valueView = IconDetailsView() - valueView.iconWidth = 24.0 - valueView.mode = .iconDetails - valueView.spacing = 8.0 - valueView.detailsLabel.textColor = R.color.colorTextPrimary() - valueView.detailsLabel.font = .regularFootnote - valueView.detailsLabel.numberOfLines = 1 - - let titleView = GenericTitleValueView(titleView: titleLabel, valueView: valueView) - let rowView = RowView(contentView: titleView, preferredHeight: 48.0) - rowView.borderView.strokeColor = R.color.colorDivider()! - rowView.borderView.strokeWidth = 0.5 - rowView.isUserInteractionEnabled = false - rowView.contentInsets = .zero - return rowView + addArrangedSubview(senderTableView, spacingAfter: 8) + senderTableView.addArrangedSubview(walletCell) + senderTableView.addArrangedSubview(accountCell) + senderTableView.addArrangedSubview(networkCell) + senderTableView.addArrangedSubview(feeCell) } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModel.swift b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModel.swift index 36ce213115..bc77b80919 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModel.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModel.swift @@ -3,6 +3,7 @@ import SubstrateSdk struct DAppOperationConfirmViewModel { let iconImageViewModel: ImageViewModelProtocol? + let dApp: String let walletName: String let walletIcon: DrawableIcon? let address: String diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift index a6502cb186..1a9249b060 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/ViewModel/DAppOperationConfirmViewModelFactory.swift @@ -37,6 +37,7 @@ final class DAppOperationConfirmViewModelFactory: DAppOperationConfirmViewModelF return DAppOperationConfirmViewModel( iconImageViewModel: iconViewModel, + dApp: model.dApp, walletName: model.accountName, walletIcon: walletIcon, address: model.chainAddress.truncated, diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index 45f5d313ce..303691ab27 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -163,7 +163,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { identifier: request.id.string, wallet: wallet, accountId: accountId, - dApp: session?.peer.name ?? "", + dApp: session?.peer.url ?? "", dAppIcon: session?.peer.icons.first.flatMap { URL(string: $0) }, operationData: operationData ) From e1a753597beb97549d877de5b10112b4193ef448 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 10 May 2023 09:39:50 +0500 Subject: [PATCH 41/54] fix tests --- novawallet.xcodeproj/project.pbxproj | 2 +- .../DAppInteractionPresenter.swift | 65 +- .../DAppOperationConfirmViewController.swift | 7 +- novawallet/en.lproj/Localizable.strings | 1 + novawallet/ru.lproj/Localizable.strings | 1 + novawalletTests/Mocks/ModuleMocks.swift | 692 ++++++++++++++---- .../Modules/Settings/SettingsTests.swift | 22 +- 7 files changed, 634 insertions(+), 156 deletions(-) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 8e5df67314..e821edc426 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -16886,7 +16886,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#! /bin/sh\n# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"${PROJECT_NAME}Tests/Mocks/ModuleMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"Pods/Cuckoo/run\" generate --no-header --testable \"${PROJECT_NAME}\" \\\n--exclude \"RootPresenterFactoryProtocol, UsernameSetupViewFactoryProtocol, OnboardingMainViewFactoryProtocol, AccountCreateViewFactoryProtocol, AccountImportViewFactoryProtocol, AccountConfirmViewFactoryProtocol, PinViewFactoryProtocol, ProfileViewFactoryProtocol, AccountManagementViewFactoryProtocol, AccountInfoViewFactoryProtocol, NetworkManagementViewFactoryProtocol, NetworkInfoViewFactoryProtocol, AddConnectionViewFactoryProtocol, AccountExportPasswordViewFactoryProtocol, ExportRestoreJsonViewFactoryProtocol, ExportMnemonicViewFactoryProtocol, StakingMainViewFactoryProtocol, SelectValidatorsStartViewFactoryProtocol, SelectValidatorsConfirmViewFactoryProtocol, RecommendedValidatorListViewFactoryProtocol, SelectedValidatorListViewFactoryProtocol, CustomValidatorListViewFactoryProtocol, ValidatorInfoViewFactoryProtocol, WalletHistoryFilterViewFactoryProtocol, StakingPayoutConfirmationViewFactoryProtocol, StakingRewardDetailsViewFactoryProtocol, StakingRewardPayoutsViewFactoryProtocol, StakingPayoutConfirmViewModelFactoryProtocol, YourValidatorListViewFactoryProtocol, StakingUnbondSetupViewFactoryProtocol, StakingUnbondConfirmViewFactoryProtocol, StakingRedeemViewFactoryProtocol, StakingBondMoreViewFactoryProtocol, StakingRebondSetupViewFactoryProtocol, ValidatorListFilterViewFactoryProtocol, ValidatorSearchViewFactoryProtocol\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"$INPUT_DIR/../Pods/SoraFoundation/SoraFoundation/Classes/Localization/Localizable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/LoadableViewProtocol.swift\" \\\n\"$INPUT_DIR/Common/Protocols/ControllerBackedProtocol.swift\" \\\n\"$INPUT_DIR/Common/Protocols/WebPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AlertPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/ModalAlertPresenting.swift\" \\\n\"$INPUT_DIR/Common/Protocols/SharingPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AccountSelectionPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AuthorizationPresentable.swift\" \\\n\"$INPUT_DIR/Common/ViewController/SearchController/TableSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Root/RootProtocol.swift\" \\\n\"$INPUT_DIR/Modules/UsernameSetup/UsernameSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/OnbordingMain/OnboardingMainProtocol.swift\" \\\n\"$INPUT_DIR/Modules/AccountCreate/AccountCreateProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountImport/AccountImportProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountConfirm/AccountConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Pincode/PinSetup/PinSetupProtocol.swift\" \\\n\"$INPUT_DIR/Modules/Settings/SettingsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountManagement/AccountManagementProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountInfo/AccountInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/NetworkManagement/NetworkManagementProtocols.swift\" \\\n\"$INPUT_DIR/Modules/NetworkInfo/NetworkInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AddConnection/AddConnectionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/AccountExportPassword/AccountExportPasswordProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportGenericView/ExportGenericProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportMnemonic/ExportMnemonicProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportRestoreJson/ExportRestoreJsonProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Wallet/HistoryFilter/WalletHistoryFilterProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/Operations/ValidatorOperationFactory/ValidatorOperationFactoryProtocol.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingMain/StakingMainProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingMain/Relaychain/StakingRelaychainProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/Operations/NetworkStakingInfoOperationFactory.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingPayoutConfirmation/StakingPayoutConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDetails/StakingRewardDetailsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardPayouts/StakingRewardPayoutsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBalance/StakingBalanceProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingUnbondConfirm/StakingUnbondConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRedeem/StakingRedeemProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBondMore/StakingBondMoreProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBondMoreConfirmation/StakingBondMoreConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRebondSetup/StakingRebondSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRebondConfirmation/StakingRebondConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/ControllerAccount/ControllerAccountProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDestinationSetup/StakingRewardDestSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDestConfirm/StakingRewardDestConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/YourValidatorList/YourValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorListFilter/ValidatorListFilterProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectValidatorsConfirm/SelectValidatorsConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/RecommendedValidatorList/RecommendedValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectedValidatorList/SelectedValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/CustomValidatorList/CustomValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorInfo/ValidatorInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanList/CrowdloanListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContribution/CrowdloanContributionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContributionSetup/CrowdloanContributionSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContributionConfirm/CrowdloanContributionConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CustomCrowdloan/CustomCrowdloanDelegate.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/ReferralCrowdloan/ReferralCrowdloanProtocols.swift\" \\\n\"$INPUT_DIR/Common/ViewController/SelectionListViewController/SelectionListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AssetSelection/AssetSelectionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AdvancedWallet/AdvancedWalletProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppList/DAppListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppBrowser/DAppBrowserProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppSearch/DAppSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AssetsSettings/AssetsSettingsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/OperationDetails/OperationDetailsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/SecurityLayer/SecurityLayerProtocols.swift\" \\\n"; + shellScript = "#! /bin/sh\n# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"${PROJECT_NAME}Tests/Mocks/ModuleMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"Pods/Cuckoo/run\" generate --no-header --testable \"${PROJECT_NAME}\" \\\n--exclude \"RootPresenterFactoryProtocol, UsernameSetupViewFactoryProtocol, OnboardingMainViewFactoryProtocol, AccountCreateViewFactoryProtocol, AccountImportViewFactoryProtocol, AccountConfirmViewFactoryProtocol, PinViewFactoryProtocol, ProfileViewFactoryProtocol, AccountManagementViewFactoryProtocol, AccountInfoViewFactoryProtocol, NetworkManagementViewFactoryProtocol, NetworkInfoViewFactoryProtocol, AddConnectionViewFactoryProtocol, AccountExportPasswordViewFactoryProtocol, ExportRestoreJsonViewFactoryProtocol, ExportMnemonicViewFactoryProtocol, StakingMainViewFactoryProtocol, SelectValidatorsStartViewFactoryProtocol, SelectValidatorsConfirmViewFactoryProtocol, RecommendedValidatorListViewFactoryProtocol, SelectedValidatorListViewFactoryProtocol, CustomValidatorListViewFactoryProtocol, ValidatorInfoViewFactoryProtocol, WalletHistoryFilterViewFactoryProtocol, StakingPayoutConfirmationViewFactoryProtocol, StakingRewardDetailsViewFactoryProtocol, StakingRewardPayoutsViewFactoryProtocol, StakingPayoutConfirmViewModelFactoryProtocol, YourValidatorListViewFactoryProtocol, StakingUnbondSetupViewFactoryProtocol, StakingUnbondConfirmViewFactoryProtocol, StakingRedeemViewFactoryProtocol, StakingBondMoreViewFactoryProtocol, StakingRebondSetupViewFactoryProtocol, ValidatorListFilterViewFactoryProtocol, ValidatorSearchViewFactoryProtocol\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"$INPUT_DIR/../Pods/SoraFoundation/SoraFoundation/Classes/Localization/Localizable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/LoadableViewProtocol.swift\" \\\n\"$INPUT_DIR/Common/Protocols/ControllerBackedProtocol.swift\" \\\n\"$INPUT_DIR/Common/Protocols/WebPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AlertPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/ModalAlertPresenting.swift\" \\\n\"$INPUT_DIR/Common/Protocols/SharingPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AccountSelectionPresentable.swift\" \\\n\"$INPUT_DIR/Common/Protocols/AuthorizationPresentable.swift\" \\\n\"$INPUT_DIR/Common/ViewController/SearchController/TableSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Root/RootProtocol.swift\" \\\n\"$INPUT_DIR/Modules/UsernameSetup/UsernameSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/OnbordingMain/OnboardingMainProtocol.swift\" \\\n\"$INPUT_DIR/Modules/AccountCreate/AccountCreateProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountImport/AccountImportProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountConfirm/AccountConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Pincode/PinSetup/PinSetupProtocol.swift\" \\\n\"$INPUT_DIR/Modules/Settings/SettingsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountManagement/AccountManagementProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AccountInfo/AccountInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/NetworkManagement/NetworkManagementProtocols.swift\" \\\n\"$INPUT_DIR/Modules/NetworkInfo/NetworkInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AddConnection/AddConnectionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/AccountExportPassword/AccountExportPasswordProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportGenericView/ExportGenericProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportMnemonic/ExportMnemonicProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Export/ExportRestoreJson/ExportRestoreJsonProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Wallet/HistoryFilter/WalletHistoryFilterProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/Operations/ValidatorOperationFactory/ValidatorOperationFactoryProtocol.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingMain/StakingMainProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingMain/Relaychain/StakingRelaychainProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/Operations/NetworkStakingInfoOperationFactory.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingPayoutConfirmation/StakingPayoutConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDetails/StakingRewardDetailsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardPayouts/StakingRewardPayoutsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBalance/StakingBalanceProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingUnbondConfirm/StakingUnbondConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRedeem/StakingRedeemProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBondMore/StakingBondMoreProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingBondMoreConfirmation/StakingBondMoreConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRebondSetup/StakingRebondSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRebondConfirmation/StakingRebondConfirmationProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/ControllerAccount/ControllerAccountProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDestinationSetup/StakingRewardDestSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/StakingRewardDestConfirm/StakingRewardDestConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/YourValidatorList/YourValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorListFilter/ValidatorListFilterProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectValidatorsConfirm/SelectValidatorsConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/RecommendedValidatorList/RecommendedValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/SelectedValidatorList/SelectedValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/CustomValidatorList/CustomValidatorListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Staking/SelectValidatorsFlow/ValidatorInfo/ValidatorInfoProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanList/CrowdloanListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContribution/CrowdloanContributionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContributionSetup/CrowdloanContributionSetupProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanContributionConfirm/CrowdloanContributionConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CustomCrowdloan/CustomCrowdloanDelegate.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/ReferralCrowdloan/ReferralCrowdloanProtocols.swift\" \\\n\"$INPUT_DIR/Common/ViewController/SelectionListViewController/SelectionListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AssetSelection/AssetSelectionProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AdvancedWallet/AdvancedWalletProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppAuthConfirm/DAppAuthConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppList/DAppListProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppBrowser/DAppBrowserProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/DAppSearch/DAppSearchProtocols.swift\" \\\n\"$INPUT_DIR/Modules/AssetsSettings/AssetsSettingsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/OperationDetails/OperationDetailsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsProtocols.swift\" \\\n\"$INPUT_DIR/Modules/SecurityLayer/SecurityLayerProtocols.swift\" \\\n\"$INPUT_DIR/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift\" \\\n"; }; 842D1E8924D207D900C30A7A /* Common Mock */ = { isa = PBXShellScriptBuildPhase; diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift index bd92a77e6a..125698605f 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift @@ -12,24 +12,46 @@ final class DAppInteractionPresenter { self.logger = logger } - private func presentDefaultAuthConfirmation(for request: DAppAuthRequest) { - guard let authVew = DAppAuthConfirmViewFactory.createView(for: request, delegate: self) else { - return - } + private func presentOverFullscreen(the controller: UIViewController) { + let navigationController = NovaNavigationController(rootViewController: controller) + navigationController.barSettings = .init(style: .defaultStyle, shouldSetCloseButton: false) + + navigationController.modalPresentationStyle = .overFullScreen + + window?.rootViewController?.topModalViewController.present( + navigationController, + animated: true, + completion: nil + ) + } + private func presentInBottomSheet(the controller: UIViewController) { let factory = ModalSheetPresentationFactory( configuration: ModalSheetPresentationConfiguration.nova ) - authVew.controller.modalTransitioningFactory = factory - authVew.controller.modalPresentationStyle = .custom + + controller.modalTransitioningFactory = factory + controller.modalPresentationStyle = .custom window?.rootViewController?.topModalViewController.present( - authVew.controller, + controller, animated: true, completion: nil ) } + private func presentDefaultAuthConfirmation(for request: DAppAuthRequest) { + guard let authVew = DAppAuthConfirmViewFactory.createView(for: request, delegate: self) else { + return + } + + presentInBottomSheet(the: authVew.controller) + } + + private func presentDefaultRequestConfirmation(view: DAppOperationConfirmViewProtocol) { + presentInBottomSheet(the: view.controller) + } + private func presentWalletConnectAuthConfirmation(for request: DAppAuthRequest) { guard let confirmationView = DAppWalletAuthViewFactory.createWalletConnectView( @@ -39,16 +61,11 @@ final class DAppInteractionPresenter { return } - let navigationController = NovaNavigationController(rootViewController: confirmationView.controller) - navigationController.barSettings = .init(style: .defaultStyle, shouldSetCloseButton: false) - - navigationController.modalPresentationStyle = .overFullScreen + presentOverFullscreen(the: confirmationView.controller) + } - window?.rootViewController?.topModalViewController.present( - navigationController, - animated: true, - completion: nil - ) + private func presentWalletConnectRequestConfirmation(view: DAppOperationConfirmViewProtocol) { + presentOverFullscreen(the: view.controller) } } @@ -62,17 +79,11 @@ extension DAppInteractionPresenter: DAppInteractionOutputProtocol { return } - let factory = ModalSheetPresentationFactory(configuration: ModalSheetPresentationConfiguration.nova - ) - - confirmationView.controller.modalTransitioningFactory = factory - confirmationView.controller.modalPresentationStyle = .custom - - window?.rootViewController?.topModalViewController.present( - confirmationView.controller, - animated: true, - completion: nil - ) + if request.transportName == DAppTransports.walletConnect { + presentWalletConnectRequestConfirmation(view: confirmationView) + } else { + presentDefaultRequestConfirmation(view: confirmationView) + } } func didReceiveAuth(request: DAppAuthRequest) { diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift index 6e8aa5f5c0..99a482fb8c 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift @@ -43,6 +43,9 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { private func setupLocalization() { let languages = selectedLocale.rLanguages + + title = R.string.localizable.dappsRequestSignTitle(preferredLanguages: languages) + rootView.titleLabel.text = R.string.localizable.commonConfirmTitle(preferredLanguages: languages) rootView.subtitleLabel.text = R.string.localizable.dappConfirmSubtitle(preferredLanguages: languages) rootView.dAppCell.titleLabel.text = R.string.localizable.commonDapp(preferredLanguages: languages) @@ -94,12 +97,12 @@ extension DAppOperationConfirmViewController: DAppOperationConfirmViewProtocol { rootView.walletCell.bind(viewModel: .init( details: confimationViewModel.walletName, - imageViewModel: confimationViewModel.walletIcon.map({ DrawableIconViewModel(icon: $0) }) + imageViewModel: confimationViewModel.walletIcon.map { DrawableIconViewModel(icon: $0) } )) rootView.accountCell.bind(viewModel: .init( details: confimationViewModel.address, - imageViewModel: confimationViewModel.addressIcon.map({ DrawableIconViewModel(icon: $0) }) + imageViewModel: confimationViewModel.addressIcon.map { DrawableIconViewModel(icon: $0) } )) rootView.networkCell.bind(viewModel: .init( diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index 2b8c31c9db..cb3cf377f4 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1221,3 +1221,4 @@ "dapps.missing.required.networks.warning.format" = "Some of the required networks requested by “%@” are not supported in Nova Wallet"; "dapps.required.networks" = "Required"; "dapps.optional.networks" = "Optional"; +"dapps.request.sign.title" = "Sign Request"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 99b44dd896..051260974d 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1221,3 +1221,4 @@ "dapps.missing.required.networks.warning.format" = "Некоторые запрошенные “%@” сети не поддерживаются в Nova Wallet"; "dapps.required.networks" = "Обязательные"; "dapps.optional.networks" = "Не обязательные"; +"dapps.request.sign.title" = "Подпишите запрос"; diff --git a/novawalletTests/Mocks/ModuleMocks.swift b/novawalletTests/Mocks/ModuleMocks.swift index 0d55a8380f..47a3c204e7 100644 --- a/novawalletTests/Mocks/ModuleMocks.swift +++ b/novawalletTests/Mocks/ModuleMocks.swift @@ -13634,6 +13634,509 @@ import RobinHood } +import Cuckoo +@testable import novawallet + + + class MockWalletConnectInteractorInputProtocol: WalletConnectInteractorInputProtocol, Cuckoo.ProtocolMock { + + typealias MocksType = WalletConnectInteractorInputProtocol + + typealias Stubbing = __StubbingProxy_WalletConnectInteractorInputProtocol + typealias Verification = __VerificationProxy_WalletConnectInteractorInputProtocol + + let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: false) + + + private var __defaultImplStub: WalletConnectInteractorInputProtocol? + + func enableDefaultImplementation(_ stub: WalletConnectInteractorInputProtocol) { + __defaultImplStub = stub + cuckoo_manager.enableDefaultStubImplementation() + } + + + + + + + + + struct __StubbingProxy_WalletConnectInteractorInputProtocol: Cuckoo.StubbingProxy { + private let cuckoo_manager: Cuckoo.MockManager + + init(manager: Cuckoo.MockManager) { + self.cuckoo_manager = manager + } + + + } + + struct __VerificationProxy_WalletConnectInteractorInputProtocol: Cuckoo.VerificationProxy { + private let cuckoo_manager: Cuckoo.MockManager + private let callMatcher: Cuckoo.CallMatcher + private let sourceLocation: Cuckoo.SourceLocation + + init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { + self.cuckoo_manager = manager + self.callMatcher = callMatcher + self.sourceLocation = sourceLocation + } + + + + + } +} + + class WalletConnectInteractorInputProtocolStub: WalletConnectInteractorInputProtocol { + + + + + +} + + + + class MockWalletConnectInteractorOutputProtocol: WalletConnectInteractorOutputProtocol, Cuckoo.ProtocolMock { + + typealias MocksType = WalletConnectInteractorOutputProtocol + + typealias Stubbing = __StubbingProxy_WalletConnectInteractorOutputProtocol + typealias Verification = __VerificationProxy_WalletConnectInteractorOutputProtocol + + let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: false) + + + private var __defaultImplStub: WalletConnectInteractorOutputProtocol? + + func enableDefaultImplementation(_ stub: WalletConnectInteractorOutputProtocol) { + __defaultImplStub = stub + cuckoo_manager.enableDefaultStubImplementation() + } + + + + + + + + + struct __StubbingProxy_WalletConnectInteractorOutputProtocol: Cuckoo.StubbingProxy { + private let cuckoo_manager: Cuckoo.MockManager + + init(manager: Cuckoo.MockManager) { + self.cuckoo_manager = manager + } + + + } + + struct __VerificationProxy_WalletConnectInteractorOutputProtocol: Cuckoo.VerificationProxy { + private let cuckoo_manager: Cuckoo.MockManager + private let callMatcher: Cuckoo.CallMatcher + private let sourceLocation: Cuckoo.SourceLocation + + init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { + self.cuckoo_manager = manager + self.callMatcher = callMatcher + self.sourceLocation = sourceLocation + } + + + + + } +} + + class WalletConnectInteractorOutputProtocolStub: WalletConnectInteractorOutputProtocol { + + + + + +} + + + + class MockWalletConnectDelegateInputProtocol: WalletConnectDelegateInputProtocol, Cuckoo.ProtocolMock { + + typealias MocksType = WalletConnectDelegateInputProtocol + + typealias Stubbing = __StubbingProxy_WalletConnectDelegateInputProtocol + typealias Verification = __VerificationProxy_WalletConnectDelegateInputProtocol + + let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: false) + + + private var __defaultImplStub: WalletConnectDelegateInputProtocol? + + func enableDefaultImplementation(_ stub: WalletConnectDelegateInputProtocol) { + __defaultImplStub = stub + cuckoo_manager.enableDefaultStubImplementation() + } + + + + + + + + + + func connect(uri: String) { + + return cuckoo_manager.call("connect(uri: String)", + parameters: (uri), + escapingParameters: (uri), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.connect(uri: uri)) + + } + + + + func add(delegate: WalletConnectDelegateOutputProtocol) { + + return cuckoo_manager.call("add(delegate: WalletConnectDelegateOutputProtocol)", + parameters: (delegate), + escapingParameters: (delegate), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.add(delegate: delegate)) + + } + + + + func remove(delegate: WalletConnectDelegateOutputProtocol) { + + return cuckoo_manager.call("remove(delegate: WalletConnectDelegateOutputProtocol)", + parameters: (delegate), + escapingParameters: (delegate), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.remove(delegate: delegate)) + + } + + + + func getSessionsCount() -> Int { + + return cuckoo_manager.call("getSessionsCount() -> Int", + parameters: (), + escapingParameters: (), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.getSessionsCount()) + + } + + + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) { + + return cuckoo_manager.call("fetchSessions(_: @escaping (Result<[WalletConnectSession], Error>) -> Void)", + parameters: (completion), + escapingParameters: (completion), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.fetchSessions(completion)) + + } + + + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) { + + return cuckoo_manager.call("disconnect(from: String, completion: @escaping (Error?) -> Void)", + parameters: (session, completion), + escapingParameters: (session, completion), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.disconnect(from: session, completion: completion)) + + } + + + struct __StubbingProxy_WalletConnectDelegateInputProtocol: Cuckoo.StubbingProxy { + private let cuckoo_manager: Cuckoo.MockManager + + init(manager: Cuckoo.MockManager) { + self.cuckoo_manager = manager + } + + + func connect(uri: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(String)> where M1.MatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "connect(uri: String)", parameterMatchers: matchers)) + } + + func add(delegate: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(WalletConnectDelegateOutputProtocol)> where M1.MatchedType == WalletConnectDelegateOutputProtocol { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectDelegateOutputProtocol)>] = [wrap(matchable: delegate) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "add(delegate: WalletConnectDelegateOutputProtocol)", parameterMatchers: matchers)) + } + + func remove(delegate: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(WalletConnectDelegateOutputProtocol)> where M1.MatchedType == WalletConnectDelegateOutputProtocol { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectDelegateOutputProtocol)>] = [wrap(matchable: delegate) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "remove(delegate: WalletConnectDelegateOutputProtocol)", parameterMatchers: matchers)) + } + + func getSessionsCount() -> Cuckoo.ProtocolStubFunction<(), Int> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "getSessionsCount() -> Int", parameterMatchers: matchers)) + } + + func fetchSessions(_ completion: M1) -> Cuckoo.ProtocolStubNoReturnFunction<((Result<[WalletConnectSession], Error>) -> Void)> where M1.MatchedType == (Result<[WalletConnectSession], Error>) -> Void { + let matchers: [Cuckoo.ParameterMatcher<((Result<[WalletConnectSession], Error>) -> Void)>] = [wrap(matchable: completion) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "fetchSessions(_: @escaping (Result<[WalletConnectSession], Error>) -> Void)", parameterMatchers: matchers)) + } + + func disconnect(from session: M1, completion: M2) -> Cuckoo.ProtocolStubNoReturnFunction<(String, (Error?) -> Void)> where M1.MatchedType == String, M2.MatchedType == (Error?) -> Void { + let matchers: [Cuckoo.ParameterMatcher<(String, (Error?) -> Void)>] = [wrap(matchable: session) { $0.0 }, wrap(matchable: completion) { $0.1 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "disconnect(from: String, completion: @escaping (Error?) -> Void)", parameterMatchers: matchers)) + } + + } + + struct __VerificationProxy_WalletConnectDelegateInputProtocol: Cuckoo.VerificationProxy { + private let cuckoo_manager: Cuckoo.MockManager + private let callMatcher: Cuckoo.CallMatcher + private let sourceLocation: Cuckoo.SourceLocation + + init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { + self.cuckoo_manager = manager + self.callMatcher = callMatcher + self.sourceLocation = sourceLocation + } + + + + + @discardableResult + func connect(uri: M1) -> Cuckoo.__DoNotUse<(String), Void> where M1.MatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] + return cuckoo_manager.verify("connect(uri: String)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func add(delegate: M1) -> Cuckoo.__DoNotUse<(WalletConnectDelegateOutputProtocol), Void> where M1.MatchedType == WalletConnectDelegateOutputProtocol { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectDelegateOutputProtocol)>] = [wrap(matchable: delegate) { $0 }] + return cuckoo_manager.verify("add(delegate: WalletConnectDelegateOutputProtocol)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func remove(delegate: M1) -> Cuckoo.__DoNotUse<(WalletConnectDelegateOutputProtocol), Void> where M1.MatchedType == WalletConnectDelegateOutputProtocol { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectDelegateOutputProtocol)>] = [wrap(matchable: delegate) { $0 }] + return cuckoo_manager.verify("remove(delegate: WalletConnectDelegateOutputProtocol)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func getSessionsCount() -> Cuckoo.__DoNotUse<(), Int> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return cuckoo_manager.verify("getSessionsCount() -> Int", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func fetchSessions(_ completion: M1) -> Cuckoo.__DoNotUse<((Result<[WalletConnectSession], Error>) -> Void), Void> where M1.MatchedType == (Result<[WalletConnectSession], Error>) -> Void { + let matchers: [Cuckoo.ParameterMatcher<((Result<[WalletConnectSession], Error>) -> Void)>] = [wrap(matchable: completion) { $0 }] + return cuckoo_manager.verify("fetchSessions(_: @escaping (Result<[WalletConnectSession], Error>) -> Void)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func disconnect(from session: M1, completion: M2) -> Cuckoo.__DoNotUse<(String, (Error?) -> Void), Void> where M1.MatchedType == String, M2.MatchedType == (Error?) -> Void { + let matchers: [Cuckoo.ParameterMatcher<(String, (Error?) -> Void)>] = [wrap(matchable: session) { $0.0 }, wrap(matchable: completion) { $0.1 }] + return cuckoo_manager.verify("disconnect(from: String, completion: @escaping (Error?) -> Void)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + } +} + + class WalletConnectDelegateInputProtocolStub: WalletConnectDelegateInputProtocol { + + + + + + + + func connect(uri: String) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + + func add(delegate: WalletConnectDelegateOutputProtocol) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + + func remove(delegate: WalletConnectDelegateOutputProtocol) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + + func getSessionsCount() -> Int { + return DefaultValueRegistry.defaultValue(for: (Int).self) + } + + + + func fetchSessions(_ completion: @escaping (Result<[WalletConnectSession], Error>) -> Void) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + + func disconnect(from session: String, completion: @escaping (Error?) -> Void) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + +} + + + + class MockWalletConnectDelegateOutputProtocol: WalletConnectDelegateOutputProtocol, Cuckoo.ProtocolMock { + + typealias MocksType = WalletConnectDelegateOutputProtocol + + typealias Stubbing = __StubbingProxy_WalletConnectDelegateOutputProtocol + typealias Verification = __VerificationProxy_WalletConnectDelegateOutputProtocol + + let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: false) + + + private var __defaultImplStub: WalletConnectDelegateOutputProtocol? + + func enableDefaultImplementation(_ stub: WalletConnectDelegateOutputProtocol) { + __defaultImplStub = stub + cuckoo_manager.enableDefaultStubImplementation() + } + + + + + + + + + + func walletConnectDidChangeSessions() { + + return cuckoo_manager.call("walletConnectDidChangeSessions()", + parameters: (), + escapingParameters: (), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.walletConnectDidChangeSessions()) + + } + + + + func walletConnectDidChangeChains() { + + return cuckoo_manager.call("walletConnectDidChangeChains()", + parameters: (), + escapingParameters: (), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.walletConnectDidChangeChains()) + + } + + + struct __StubbingProxy_WalletConnectDelegateOutputProtocol: Cuckoo.StubbingProxy { + private let cuckoo_manager: Cuckoo.MockManager + + init(manager: Cuckoo.MockManager) { + self.cuckoo_manager = manager + } + + + func walletConnectDidChangeSessions() -> Cuckoo.ProtocolStubNoReturnFunction<()> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateOutputProtocol.self, method: "walletConnectDidChangeSessions()", parameterMatchers: matchers)) + } + + func walletConnectDidChangeChains() -> Cuckoo.ProtocolStubNoReturnFunction<()> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateOutputProtocol.self, method: "walletConnectDidChangeChains()", parameterMatchers: matchers)) + } + + } + + struct __VerificationProxy_WalletConnectDelegateOutputProtocol: Cuckoo.VerificationProxy { + private let cuckoo_manager: Cuckoo.MockManager + private let callMatcher: Cuckoo.CallMatcher + private let sourceLocation: Cuckoo.SourceLocation + + init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { + self.cuckoo_manager = manager + self.callMatcher = callMatcher + self.sourceLocation = sourceLocation + } + + + + + @discardableResult + func walletConnectDidChangeSessions() -> Cuckoo.__DoNotUse<(), Void> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return cuckoo_manager.verify("walletConnectDidChangeSessions()", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + @discardableResult + func walletConnectDidChangeChains() -> Cuckoo.__DoNotUse<(), Void> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return cuckoo_manager.verify("walletConnectDidChangeChains()", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + + } +} + + class WalletConnectDelegateOutputProtocolStub: WalletConnectDelegateOutputProtocol { + + + + + + + + func walletConnectDidChangeSessions() { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + + + + func walletConnectDidChangeChains() { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + +} + + import Cuckoo @testable import novawallet @@ -19546,131 +20049,6 @@ import UIKit.UIImage - class MockSettingsViewModelFactoryProtocol: SettingsViewModelFactoryProtocol, Cuckoo.ProtocolMock { - - typealias MocksType = SettingsViewModelFactoryProtocol - - typealias Stubbing = __StubbingProxy_SettingsViewModelFactoryProtocol - typealias Verification = __VerificationProxy_SettingsViewModelFactoryProtocol - - let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: false) - - - private var __defaultImplStub: SettingsViewModelFactoryProtocol? - - func enableDefaultImplementation(_ stub: SettingsViewModelFactoryProtocol) { - __defaultImplStub = stub - cuckoo_manager.enableDefaultStubImplementation() - } - - - - - - - - - - func createAccountViewModel(for wallet: MetaAccountModel) -> SettingsAccountViewModel { - - return cuckoo_manager.call("createAccountViewModel(for: MetaAccountModel) -> SettingsAccountViewModel", - parameters: (wallet), - escapingParameters: (wallet), - superclassCall: - - Cuckoo.MockManager.crashOnProtocolSuperclassCall() - , - defaultCall: __defaultImplStub!.createAccountViewModel(for: wallet)) - - } - - - - func createSectionViewModels(language: Language?, currency: String?, locale: Locale) -> [(SettingsSection, [SettingsCellViewModel])] { - - return cuckoo_manager.call("createSectionViewModels(language: Language?, currency: String?, locale: Locale) -> [(SettingsSection, [SettingsCellViewModel])]", - parameters: (language, currency, locale), - escapingParameters: (language, currency, locale), - superclassCall: - - Cuckoo.MockManager.crashOnProtocolSuperclassCall() - , - defaultCall: __defaultImplStub!.createSectionViewModels(language: language, currency: currency, locale: locale)) - - } - - - struct __StubbingProxy_SettingsViewModelFactoryProtocol: Cuckoo.StubbingProxy { - private let cuckoo_manager: Cuckoo.MockManager - - init(manager: Cuckoo.MockManager) { - self.cuckoo_manager = manager - } - - - func createAccountViewModel(for wallet: M1) -> Cuckoo.ProtocolStubFunction<(MetaAccountModel), SettingsAccountViewModel> where M1.MatchedType == MetaAccountModel { - let matchers: [Cuckoo.ParameterMatcher<(MetaAccountModel)>] = [wrap(matchable: wallet) { $0 }] - return .init(stub: cuckoo_manager.createStub(for: MockSettingsViewModelFactoryProtocol.self, method: "createAccountViewModel(for: MetaAccountModel) -> SettingsAccountViewModel", parameterMatchers: matchers)) - } - - func createSectionViewModels(language: M1, currency: M2, locale: M3) -> Cuckoo.ProtocolStubFunction<(Language?, String?, Locale), [(SettingsSection, [SettingsCellViewModel])]> where M1.OptionalMatchedType == Language, M2.OptionalMatchedType == String, M3.MatchedType == Locale { - let matchers: [Cuckoo.ParameterMatcher<(Language?, String?, Locale)>] = [wrap(matchable: language) { $0.0 }, wrap(matchable: currency) { $0.1 }, wrap(matchable: locale) { $0.2 }] - return .init(stub: cuckoo_manager.createStub(for: MockSettingsViewModelFactoryProtocol.self, method: "createSectionViewModels(language: Language?, currency: String?, locale: Locale) -> [(SettingsSection, [SettingsCellViewModel])]", parameterMatchers: matchers)) - } - - } - - struct __VerificationProxy_SettingsViewModelFactoryProtocol: Cuckoo.VerificationProxy { - private let cuckoo_manager: Cuckoo.MockManager - private let callMatcher: Cuckoo.CallMatcher - private let sourceLocation: Cuckoo.SourceLocation - - init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) { - self.cuckoo_manager = manager - self.callMatcher = callMatcher - self.sourceLocation = sourceLocation - } - - - - - @discardableResult - func createAccountViewModel(for wallet: M1) -> Cuckoo.__DoNotUse<(MetaAccountModel), SettingsAccountViewModel> where M1.MatchedType == MetaAccountModel { - let matchers: [Cuckoo.ParameterMatcher<(MetaAccountModel)>] = [wrap(matchable: wallet) { $0 }] - return cuckoo_manager.verify("createAccountViewModel(for: MetaAccountModel) -> SettingsAccountViewModel", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) - } - - @discardableResult - func createSectionViewModels(language: M1, currency: M2, locale: M3) -> Cuckoo.__DoNotUse<(Language?, String?, Locale), [(SettingsSection, [SettingsCellViewModel])]> where M1.OptionalMatchedType == Language, M2.OptionalMatchedType == String, M3.MatchedType == Locale { - let matchers: [Cuckoo.ParameterMatcher<(Language?, String?, Locale)>] = [wrap(matchable: language) { $0.0 }, wrap(matchable: currency) { $0.1 }, wrap(matchable: locale) { $0.2 }] - return cuckoo_manager.verify("createSectionViewModels(language: Language?, currency: String?, locale: Locale) -> [(SettingsSection, [SettingsCellViewModel])]", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) - } - - } -} - - class SettingsViewModelFactoryProtocolStub: SettingsViewModelFactoryProtocol { - - - - - - - - func createAccountViewModel(for wallet: MetaAccountModel) -> SettingsAccountViewModel { - return DefaultValueRegistry.defaultValue(for: (SettingsAccountViewModel).self) - } - - - - func createSectionViewModels(language: Language?, currency: String?, locale: Locale) -> [(SettingsSection, [SettingsCellViewModel])] { - return DefaultValueRegistry.defaultValue(for: ([(SettingsSection, [SettingsCellViewModel])]).self) - } - -} - - - class MockSettingsInteractorInputProtocol: SettingsInteractorInputProtocol, Cuckoo.ProtocolMock { typealias MocksType = SettingsInteractorInputProtocol @@ -19709,6 +20087,21 @@ import UIKit.UIImage } + + + func connectWalletConnect(uri: String) { + + return cuckoo_manager.call("connectWalletConnect(uri: String)", + parameters: (uri), + escapingParameters: (uri), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.connectWalletConnect(uri: uri)) + + } + struct __StubbingProxy_SettingsInteractorInputProtocol: Cuckoo.StubbingProxy { private let cuckoo_manager: Cuckoo.MockManager @@ -19723,6 +20116,11 @@ import UIKit.UIImage return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorInputProtocol.self, method: "setup()", parameterMatchers: matchers)) } + func connectWalletConnect(uri: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(String)> where M1.MatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorInputProtocol.self, method: "connectWalletConnect(uri: String)", parameterMatchers: matchers)) + } + } struct __VerificationProxy_SettingsInteractorInputProtocol: Cuckoo.VerificationProxy { @@ -19745,6 +20143,12 @@ import UIKit.UIImage return cuckoo_manager.verify("setup()", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } + @discardableResult + func connectWalletConnect(uri: M1) -> Cuckoo.__DoNotUse<(String), Void> where M1.MatchedType == String { + let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] + return cuckoo_manager.verify("connectWalletConnect(uri: String)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + } } @@ -19760,6 +20164,12 @@ import UIKit.UIImage return DefaultValueRegistry.defaultValue(for: (Void).self) } + + + func connectWalletConnect(uri: String) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + } @@ -19832,6 +20242,21 @@ import UIKit.UIImage } + + + func didReceiveWalletConnect(sessionsCount: Int) { + + return cuckoo_manager.call("didReceiveWalletConnect(sessionsCount: Int)", + parameters: (sessionsCount), + escapingParameters: (sessionsCount), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.didReceiveWalletConnect(sessionsCount: sessionsCount)) + + } + struct __StubbingProxy_SettingsInteractorOutputProtocol: Cuckoo.StubbingProxy { private let cuckoo_manager: Cuckoo.MockManager @@ -19856,6 +20281,11 @@ import UIKit.UIImage return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorOutputProtocol.self, method: "didReceive(currencyCode: String)", parameterMatchers: matchers)) } + func didReceiveWalletConnect(sessionsCount: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(Int)> where M1.MatchedType == Int { + let matchers: [Cuckoo.ParameterMatcher<(Int)>] = [wrap(matchable: sessionsCount) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorOutputProtocol.self, method: "didReceiveWalletConnect(sessionsCount: Int)", parameterMatchers: matchers)) + } + } struct __VerificationProxy_SettingsInteractorOutputProtocol: Cuckoo.VerificationProxy { @@ -19890,6 +20320,12 @@ import UIKit.UIImage return cuckoo_manager.verify("didReceive(currencyCode: String)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } + @discardableResult + func didReceiveWalletConnect(sessionsCount: M1) -> Cuckoo.__DoNotUse<(Int), Void> where M1.MatchedType == Int { + let matchers: [Cuckoo.ParameterMatcher<(Int)>] = [wrap(matchable: sessionsCount) { $0 }] + return cuckoo_manager.verify("didReceiveWalletConnect(sessionsCount: Int)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + } } @@ -19917,6 +20353,12 @@ import UIKit.UIImage return DefaultValueRegistry.defaultValue(for: (Void).self) } + + + func didReceiveWalletConnect(sessionsCount: Int) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + } diff --git a/novawalletTests/Modules/Settings/SettingsTests.swift b/novawalletTests/Modules/Settings/SettingsTests.swift index 0e20bc6623..2993c1319a 100644 --- a/novawalletTests/Modules/Settings/SettingsTests.swift +++ b/novawalletTests/Modules/Settings/SettingsTests.swift @@ -40,13 +40,33 @@ final class SettingsTests: XCTestCase { let wireframe = MockSettingsWireframeProtocol() let eventCenter = MockEventCenterProtocol() + + let walletConnect = MockWalletConnectDelegateInputProtocol() + + stub(walletConnect) { stub in + when(stub).add(delegate: any()).thenDoNothing() + when(stub).connect(uri: any()).thenDoNothing() + when(stub).remove(delegate: any()).thenDoNothing() + when(stub).getSessionsCount().thenReturn(0) + when(stub).fetchSessions(any()).then { closure in + closure(.success([])) + } + when(stub).disconnect(from: any(), completion: any()).then { session, completion in + completion(nil) + } + } + let interactor = SettingsInteractor( selectedWalletSettings: walletSettings, eventCenter: eventCenter, + walletConnect: walletConnect, currencyManager: CurrencyManagerStub() ) - let viewModelFactory = SettingsViewModelFactory(iconGenerator: NovaIconGenerator()) + let viewModelFactory = SettingsViewModelFactory( + iconGenerator: NovaIconGenerator(), + quantityFormatter: NumberFormatter.quantity.localizableResource() + ) let presenter = SettingsPresenter( viewModelFactory: viewModelFactory, From 02a3c2a2f33a75e0b6332fd658d327b671ba349d Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 10 May 2023 14:12:04 +0500 Subject: [PATCH 42/54] fix confirm screen redesign --- .../View/StackTable/StackActionView.swift | 26 +++++------ .../View/StackTable/StackTableCell.swift | 39 +++++++++++++---- .../DAppBrowser/DAppBrowserWireframe.swift | 6 --- .../DAppInteractionPresenter.swift | 10 ++--- .../DAppOperationConfirmPresenter.swift | 21 ++++++++- .../DAppOperationConfirmProtocols.swift | 5 ++- .../DAppOperationConfirmViewController.swift | 43 +++++++++++-------- .../DAppOperationConfirmViewFactory.swift | 1 + .../DAppOperationConfirmViewLayout.swift | 4 ++ .../Modules/DApp/Model/DAppUnknownChain.swift | 9 ++++ 10 files changed, 111 insertions(+), 53 deletions(-) diff --git a/novawallet/Common/View/StackTable/StackActionView.swift b/novawallet/Common/View/StackTable/StackActionView.swift index 78cd458564..12fc994400 100644 --- a/novawallet/Common/View/StackTable/StackActionView.swift +++ b/novawallet/Common/View/StackTable/StackActionView.swift @@ -22,11 +22,7 @@ final class StackActionView: UIView { make.height.equalTo(iconSize) } - let iconOffset = iconSize > 0 ? 12 : 0 - - titleLabel.snp.updateConstraints { make in - make.leading.equalTo(iconImageView.snp.trailing).offset(iconOffset) - } + updateTitleLayout() } } @@ -37,6 +33,10 @@ final class StackActionView: UIView { return imageView }() + private var titleOffset: CGFloat { + iconSize > 0 ? 12 : 0 + } + override init(frame: CGRect) { super.init(frame: frame) @@ -58,12 +58,7 @@ final class StackActionView: UIView { addSubview(view) - let iconOffset: CGFloat = iconSize > 0 ? 12 : 0 - - titleLabel.snp.remakeConstraints { make in - make.leading.equalTo(iconImageView.snp.trailing).offset(iconOffset) - make.centerY.equalToSuperview() - } + updateTitleLayout() disclosureIndicatorView.snp.remakeConstraints { make in make.trailing.equalToSuperview() @@ -89,7 +84,7 @@ final class StackActionView: UIView { addSubview(titleLabel) titleLabel.snp.makeConstraints { make in - make.leading.equalTo(iconImageView.snp.trailing).offset(12.0) + make.leading.equalToSuperview().offset(iconSize + titleOffset) make.centerY.equalToSuperview() } @@ -102,4 +97,11 @@ final class StackActionView: UIView { titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) } + + private func updateTitleLayout() { + titleLabel.snp.remakeConstraints { make in + make.leading.equalToSuperview().offset(iconSize + titleOffset) + make.centerY.equalToSuperview() + } + } } diff --git a/novawallet/Common/View/StackTable/StackTableCell.swift b/novawallet/Common/View/StackTable/StackTableCell.swift index 874a198750..b2950116bf 100644 --- a/novawallet/Common/View/StackTable/StackTableCell.swift +++ b/novawallet/Common/View/StackTable/StackTableCell.swift @@ -20,14 +20,26 @@ class StackTableCell: RowView> { } func bind(viewModel: StackCellViewModel?) { - bind(details: viewModel?.details, imageViewModel: viewModel?.imageViewModel) + bind( + details: viewModel?.details, + imageViewModel: viewModel?.imageViewModel, + cornerRadius: rowContentView.valueView.iconWidth / 2.0 + ) + } + + func bind(viewModel: StackCellViewModel?, cornerRadius: CGFloat?) { + bind( + details: viewModel?.details, + imageViewModel: viewModel?.imageViewModel, + cornerRadius: cornerRadius + ) } func bind(details: String) { - bind(details: details, imageViewModel: nil) + bind(details: details, imageViewModel: nil, cornerRadius: nil) } - private func bind(details: String?, imageViewModel: ImageViewModelProtocol?) { + private func bind(details: String?, imageViewModel: ImageViewModelProtocol?, cornerRadius: CGFloat?) { self.imageViewModel?.cancel(on: iconImageView) self.imageViewModel = imageViewModel @@ -36,12 +48,21 @@ class StackTableCell: RowView> { iconImageView.image = nil let imageSize = rowContentView.valueView.iconWidth - imageViewModel?.loadImage( - on: iconImageView, - targetSize: CGSize(width: imageSize, height: imageSize), - cornerRadius: imageSize / 2.0, - animated: true - ) + + if let cornerRadius = cornerRadius { + imageViewModel?.loadImage( + on: iconImageView, + targetSize: CGSize(width: imageSize, height: imageSize), + cornerRadius: cornerRadius, + animated: true + ) + } else { + imageViewModel?.loadImage( + on: iconImageView, + targetSize: CGSize(width: imageSize, height: imageSize), + animated: true + ) + } } private func configureStyle() { diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWireframe.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWireframe.swift index fdb3814a25..a06dd4520d 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWireframe.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWireframe.swift @@ -16,12 +16,6 @@ final class DAppBrowserWireframe: DAppBrowserWireframeProtocol { return } - let factory = ModalSheetPresentationFactory(configuration: ModalSheetPresentationConfiguration.nova - ) - - confirmationView.controller.modalTransitioningFactory = factory - confirmationView.controller.modalPresentationStyle = .custom - view?.controller.present(confirmationView.controller, animated: true, completion: nil) } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift index 125698605f..c88899c256 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift @@ -12,11 +12,11 @@ final class DAppInteractionPresenter { self.logger = logger } - private func presentOverFullscreen(the controller: UIViewController) { + private func presentModal(the controller: UIViewController, style: UIModalPresentationStyle = .overFullScreen) { let navigationController = NovaNavigationController(rootViewController: controller) navigationController.barSettings = .init(style: .defaultStyle, shouldSetCloseButton: false) - navigationController.modalPresentationStyle = .overFullScreen + navigationController.modalPresentationStyle = style window?.rootViewController?.topModalViewController.present( navigationController, @@ -49,7 +49,7 @@ final class DAppInteractionPresenter { } private func presentDefaultRequestConfirmation(view: DAppOperationConfirmViewProtocol) { - presentInBottomSheet(the: view.controller) + presentModal(the: view.controller, style: .automatic) } private func presentWalletConnectAuthConfirmation(for request: DAppAuthRequest) { @@ -61,11 +61,11 @@ final class DAppInteractionPresenter { return } - presentOverFullscreen(the: confirmationView.controller) + presentModal(the: confirmationView.controller) } private func presentWalletConnectRequestConfirmation(view: DAppOperationConfirmViewProtocol) { - presentOverFullscreen(the: view.controller) + presentModal(the: view.controller) } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift index 6033c9ad1a..afa279a5de 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmPresenter.swift @@ -8,6 +8,7 @@ final class DAppOperationConfirmPresenter { let wireframe: DAppOperationConfirmWireframeProtocol let interactor: DAppOperationConfirmInteractorInputProtocol let logger: LoggerProtocol? + let chain: DAppEitherChain private(set) weak var delegate: DAppOperationConfirmDelegate? @@ -24,6 +25,7 @@ final class DAppOperationConfirmPresenter { delegate: DAppOperationConfirmDelegate, viewModelFactory: DAppOperationConfirmViewModelFactoryProtocol, balanceViewModelFactory: BalanceViewModelFactoryProtocol, + chain: DAppEitherChain, localizationManager: LocalizationManagerProtocol, logger: LoggerProtocol? = nil ) { @@ -32,6 +34,7 @@ final class DAppOperationConfirmPresenter { self.delegate = delegate self.viewModelFactory = viewModelFactory self.balanceViewModelFactory = balanceViewModelFactory + self.chain = chain self.logger = logger self.localizationManager = localizationManager @@ -43,7 +46,7 @@ final class DAppOperationConfirmPresenter { } let viewModel = viewModelFactory.createViewModel(from: model) - view?.didReceive(confimationViewModel: viewModel) + view?.didReceive(confirmationViewModel: viewModel) } private func provideFeeViewModel() { @@ -107,6 +110,22 @@ extension DAppOperationConfirmPresenter: DAppOperationConfirmPresenterProtocol { func activateTxDetails() { interactor.prepareTxDetails() } + + func showAccountOptions() { + guard + let address = confirmationModel?.chainAddress, + let chain = chain.nativeChain, + let view = view else { + return + } + + wireframe.presentAccountOptions( + from: view, + address: address, + chain: chain, + locale: selectedLocale + ) + } } extension DAppOperationConfirmPresenter: DAppOperationConfirmInteractorOutputProtocol { diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift index b10a64d099..63894971ab 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmProtocols.swift @@ -1,7 +1,7 @@ import SubstrateSdk protocol DAppOperationConfirmViewProtocol: ControllerBackedProtocol { - func didReceive(confimationViewModel: DAppOperationConfirmViewModel) + func didReceive(confirmationViewModel: DAppOperationConfirmViewModel) func didReceive(feeViewModel: DAppOperationFeeViewModel) } @@ -10,6 +10,7 @@ protocol DAppOperationConfirmPresenterProtocol: AnyObject { func confirm() func reject() func activateTxDetails() + func showAccountOptions() } protocol DAppOperationConfirmInteractorInputProtocol: AnyObject { @@ -29,7 +30,7 @@ protocol DAppOperationConfirmInteractorOutputProtocol: AnyObject { } protocol DAppOperationConfirmWireframeProtocol: AlertPresentable, ErrorPresentable, FeeRetryable, - MessageSheetPresentable { + MessageSheetPresentable, AddressOptionsPresentable { func close(view: DAppOperationConfirmViewProtocol?) func showTxDetails(from view: DAppOperationConfirmViewProtocol?, json: JSON) } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift index 99a482fb8c..4c3c2c9e03 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift @@ -11,12 +11,9 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { self.presenter = presenter super.init(nibName: nil, bundle: nil) - preferredContentSize = CGSize(width: 0.0, height: 522.0) self.localizationManager = localizationManager } - private var viewModel: DAppOperationConfirmViewModel? - @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -36,6 +33,9 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { } private func setupHandlers() { + presentationController?.delegate = self + + rootView.accountCell.addTarget(self, action: #selector(actionShowAccountOptions), for: .touchUpInside) rootView.confirmButton.addTarget(self, action: #selector(actionConfirm), for: .touchUpInside) rootView.rejectButton.addTarget(self, action: #selector(actionReject), for: .touchUpInside) rootView.transactionDetailsCell.addTarget(self, action: #selector(actionTxDetails), for: .touchUpInside) @@ -84,31 +84,38 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { @objc private func actionTxDetails() { presenter.activateTxDetails() } + + @objc func actionShowAccountOptions() { + presenter.showAccountOptions() + } } extension DAppOperationConfirmViewController: DAppOperationConfirmViewProtocol { - func didReceive(confimationViewModel: DAppOperationConfirmViewModel) { - rootView.dAppCell.bind(details: confimationViewModel.dApp) + func didReceive(confirmationViewModel: DAppOperationConfirmViewModel) { + rootView.dAppCell.bind(details: confirmationViewModel.dApp) rootView.iconView.bind( - viewModel: viewModel?.iconImageViewModel, + viewModel: confirmationViewModel.iconImageViewModel, size: DAppIconLargeConstants.displaySize ) rootView.walletCell.bind(viewModel: .init( - details: confimationViewModel.walletName, - imageViewModel: confimationViewModel.walletIcon.map { DrawableIconViewModel(icon: $0) } + details: confirmationViewModel.walletName, + imageViewModel: confirmationViewModel.walletIcon.map { DrawableIconViewModel(icon: $0) } )) rootView.accountCell.bind(viewModel: .init( - details: confimationViewModel.address, - imageViewModel: confimationViewModel.addressIcon.map { DrawableIconViewModel(icon: $0) } + details: confirmationViewModel.address, + imageViewModel: confirmationViewModel.addressIcon.map { DrawableIconViewModel(icon: $0) } )) - rootView.networkCell.bind(viewModel: .init( - details: confimationViewModel.networkName, - imageViewModel: confimationViewModel.networkIconViewModel - )) + rootView.networkCell.bind( + viewModel: .init( + details: confirmationViewModel.networkName, + imageViewModel: confirmationViewModel.networkIconViewModel + ), + cornerRadius: nil + ) } func didReceive(feeViewModel: DAppOperationFeeViewModel) { @@ -133,8 +140,8 @@ extension DAppOperationConfirmViewController: Localizable { } } -extension DAppOperationConfirmViewController: ModalPresenterDelegate { - func presenterShouldHide(_: ModalPresenterProtocol) -> Bool { false } - - func presenterDidHide(_: ModalPresenterProtocol) {} +extension DAppOperationConfirmViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerShouldDismiss(_: UIPresentationController) -> Bool { + false + } } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift index 50555a3bb7..6308959ee4 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewFactory.swift @@ -64,6 +64,7 @@ struct DAppOperationConfirmViewFactory { delegate: delegate, viewModelFactory: DAppOperationConfirmViewModelFactory(chain: chain), balanceViewModelFactory: balanceViewModelFactory, + chain: chain, localizationManager: LocalizationManager.shared, logger: Logger.shared ) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift index 10c3db9642..90c36a5ab3 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewLayout.swift @@ -43,6 +43,7 @@ final class DAppOperationConfirmViewLayout: SCGenericActionLayoutView Date: Thu, 11 May 2023 09:08:51 +0500 Subject: [PATCH 43/54] fix wallet connect metadata --- novawallet.xcodeproj/project.pbxproj | 6 +++++- novawallet/CIKeys.stencil | 6 +++++- novawallet/Common/Configs/ApplicationConfigs.swift | 6 ------ .../Services/WalletConnect/WalletConnectMetadata.swift | 4 ++-- .../Services/WalletConnect/WalletConnectSecret.swift | 7 +++++++ .../Service/WalletConnectServiceFactory.swift | 2 +- 6 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 novawallet/Common/Services/WalletConnect/WalletConnectSecret.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index e821edc426..1008b032cd 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1174,6 +1174,7 @@ 845BB8D125E45F1300E5FCDC /* NominateCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845BB8D025E45F1300E5FCDC /* NominateCall.swift */; }; 845BB8D625E461FC00E5FCDC /* RewardDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845BB8D525E461FC00E5FCDC /* RewardDestination.swift */; }; 845BB8DB25E462B100E5FCDC /* SubstrateConstansts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845BB8DA25E462B100E5FCDC /* SubstrateConstansts.swift */; }; + 845C068E2A0C9E6600DAF4DF /* WalletConnectSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845C068D2A0C9E6600DAF4DF /* WalletConnectSecret.swift */; }; 845C40792702571200BFA50B /* StakingRemoteSubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845C40782702571200BFA50B /* StakingRemoteSubscriptionService.swift */; }; 845C407B27027AA000BFA50B /* SubscriptionStorageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845C407A27027AA000BFA50B /* SubscriptionStorageKeys.swift */; }; 845C407D2702812E00BFA50B /* StakingAccountUpdatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845C407C2702812E00BFA50B /* StakingAccountUpdatingService.swift */; }; @@ -4669,6 +4670,7 @@ 845BB8D025E45F1300E5FCDC /* NominateCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NominateCall.swift; sourceTree = ""; }; 845BB8D525E461FC00E5FCDC /* RewardDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardDestination.swift; sourceTree = ""; }; 845BB8DA25E462B100E5FCDC /* SubstrateConstansts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateConstansts.swift; sourceTree = ""; }; + 845C068D2A0C9E6600DAF4DF /* WalletConnectSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSecret.swift; sourceTree = ""; }; 845C40782702571200BFA50B /* StakingRemoteSubscriptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingRemoteSubscriptionService.swift; sourceTree = ""; }; 845C407A27027AA000BFA50B /* SubscriptionStorageKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionStorageKeys.swift; sourceTree = ""; }; 845C407C2702812E00BFA50B /* StakingAccountUpdatingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingAccountUpdatingService.swift; sourceTree = ""; }; @@ -10790,6 +10792,7 @@ 8490111329E68FCB005D688B /* WalletConnectService.swift */, 8441D3C829E8015100070BA3 /* WalletConnectMetadata.swift */, 840EAE6629FA8F0000453C7E /* WalletConnectDecision.swift */, + 845C068D2A0C9E6600DAF4DF /* WalletConnectSecret.swift */, ); path = WalletConnect; sourceTree = ""; @@ -16962,7 +16965,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#check if env-vars.sh exists\nif [ -f $PROJECT_DIR/$PROJECT_NAME/env-vars.sh ]\nthen\nsource $PROJECT_DIR/$PROJECT_NAME/env-vars.sh\nfi\n#no `else` case needed if the CI works as expected\n\nWORK_DIR=\"$PROJECT_DIR/$PROJECT_NAME\"\necho \"Sourcery Work Directory = $WORK_DIR\"\n\nOUT_FILE=\"$PROJECT_DIR/CIKeys.generated.swift\"\necho \"Sourcery Output File = $OUT_FILE\"\n\n\"$PODS_ROOT/Sourcery/bin/sourcery\" --templates \"$WORK_DIR\" --sources \"$WORK_DIR\" --output \"$OUT_FILE\" --args mercuryoSecretKey=$MERCURYO_PRODUCTION_SECRET,mercuryoTestSecretKey=$MERCURYO_TEST_SECRET,acalaAuthToken=$ACALA_AUTH_TOKEN,moonbeamHistoryApiKey=$MOONBEAM_HISTORY_API_KEY,moonriverHistoryApiKey=$MOONRIVER_HISTORY_API_KEY,etherscanHistoryApiKey=$ETHERSCAN_HISTORY_API_KEY,acalaAuthTestToken=$ACALA_TEST_AUTH_TOKEN,moonbeamApiKey=$MOONBEAM_API_KEY,moonbeamApiTestKey=$MOONBEAM_TEST_API_KEY,infuraApiKey=$INFURA_API_KEY\n"; + shellScript = "#check if env-vars.sh exists\nif [ -f $PROJECT_DIR/$PROJECT_NAME/env-vars.sh ]\nthen\nsource $PROJECT_DIR/$PROJECT_NAME/env-vars.sh\nfi\n#no `else` case needed if the CI works as expected\n\nWORK_DIR=\"$PROJECT_DIR/$PROJECT_NAME\"\necho \"Sourcery Work Directory = $WORK_DIR\"\n\nOUT_FILE=\"$PROJECT_DIR/CIKeys.generated.swift\"\necho \"Sourcery Output File = $OUT_FILE\"\n\n\"$PODS_ROOT/Sourcery/bin/sourcery\" --templates \"$WORK_DIR\" --sources \"$WORK_DIR\" --output \"$OUT_FILE\" --args mercuryoSecretKey=$MERCURYO_PRODUCTION_SECRET,mercuryoTestSecretKey=$MERCURYO_TEST_SECRET,acalaAuthToken=$ACALA_AUTH_TOKEN,moonbeamHistoryApiKey=$MOONBEAM_HISTORY_API_KEY,moonriverHistoryApiKey=$MOONRIVER_HISTORY_API_KEY,etherscanHistoryApiKey=$ETHERSCAN_HISTORY_API_KEY,acalaAuthTestToken=$ACALA_TEST_AUTH_TOKEN,moonbeamApiKey=$MOONBEAM_API_KEY,moonbeamApiTestKey=$MOONBEAM_TEST_API_KEY,infuraApiKey=$INFURA_API_KEY,wcProjectId=$WC_PROJECT_ID\n"; }; BB864FD3004FEB055054B4A1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -20263,6 +20266,7 @@ D7D91A2CACE6FE12AE634BEF /* DAppWalletAuthViewLayout.swift in Sources */, 3F3AE7490C59A0CE0BF2D7A7 /* DAppWalletAuthViewFactory.swift in Sources */, CDD016AB97CF4619B9A17B3E /* WalletsChooseProtocols.swift in Sources */, + 845C068E2A0C9E6600DAF4DF /* WalletConnectSecret.swift in Sources */, 3C6C738F4AB7AC6FEA290D59 /* WalletsChoosePresenter.swift in Sources */, DD3EB45488B37F390718F407 /* WalletsChooseViewController.swift in Sources */, ); diff --git a/novawallet/CIKeys.stencil b/novawallet/CIKeys.stencil index e566a52931..04649568ed 100644 --- a/novawallet/CIKeys.stencil +++ b/novawallet/CIKeys.stencil @@ -21,4 +21,8 @@ enum MoonbeamCrowdloanCIKeys { enum ConnectionApiKeys { static var infuraApiKey: String = "{{ argument.infuraApiKey }}" -} \ No newline at end of file +} + +enum WalletConnectCISecrets { + static var projectId: String = "{{ argument.wcProjectId }}" +} diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index b76beb0dd2..0141d08a07 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -35,7 +35,6 @@ protocol ApplicationConfigProtocol { var inAppUpdatesEntrypointURL: URL { get } var inAppUpdatesChangelogsURL: URL { get } var slip44URL: URL { get } - var walletConnectProjectId: String { get } } final class ApplicationConfig { @@ -231,9 +230,4 @@ extension ApplicationConfig: ApplicationConfigProtocol { } // swiftlint:enable line_length - - // TODO: Consider to make secret - var walletConnectProjectId: String { - "f28de7baff5ecce73ed0c9f9f97dbd30" - } } diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift b/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift index 1a06203d9e..187d5d3375 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectMetadata.swift @@ -13,9 +13,9 @@ extension WalletConnectMetadata { .init( projectId: projectId, name: "Nova wallet", - description: "Non-custodial Polkadot & Kusama wallet", + description: "Next-gen wallet for Polkadot and Kusama ecosystem", website: "https://novawallet.io", - icon: "https://github.com/nova-wallet/branding/raw/master/logos/Nova_Wallet_Horizontal_On_White_200px.png" + icon: "https://raw.githubusercontent.com/nova-wallet/branding/master/logos/Nova_Wallet_Star_Color.png" ) } } diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectSecret.swift b/novawallet/Common/Services/WalletConnect/WalletConnectSecret.swift new file mode 100644 index 0000000000..76bd3fd799 --- /dev/null +++ b/novawallet/Common/Services/WalletConnect/WalletConnectSecret.swift @@ -0,0 +1,7 @@ +import Foundation + +enum WalletConnectSecret { + static func getProjectId() -> String { + EnviromentVariables.variable(named: "WC_PROJECT_ID") ?? WalletConnectCISecrets.projectId + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift index 6907ba7714..c4a9d6e09a 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift @@ -8,7 +8,7 @@ struct WalletConnectServiceFactory { walletsRepository: AnyDataProviderRepository, operationQueue: OperationQueue ) -> WalletConnectInteractor { - let metadata = WalletConnectMetadata.nova(with: ApplicationConfig.shared.walletConnectProjectId) + let metadata = WalletConnectMetadata.nova(with: WalletConnectSecret.getProjectId()) let service = WalletConnectService(metadata: metadata) let dataSource = DAppStateDataSource( From d7faa0864947086dc3d0196635666ca4b2de3476 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 10:33:28 +0500 Subject: [PATCH 44/54] error handling on interactino level --- .../DAppInteractionError.swift | 20 ++++++++++- .../DAppInteractionFactory.swift | 6 +++- .../DAppInteractionMediator.swift | 23 +++++++----- .../DAppInteractionPresenter.swift | 11 ++++-- .../DAppInteractionProtocols.swift | 3 ++ .../Service/WalletConnectInteractor.swift | 4 +++ .../Transport/WalletConnectTransport.swift | 35 +++++++++++++++---- 7 files changed, 83 insertions(+), 19 deletions(-) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift index 8acbcc5b1f..3cd7f3e2bd 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift @@ -1,5 +1,23 @@ import Foundation enum DAppInteractionError: Error { - case phishingVerifierFailed(Error) + case unexpected(reason: String, internalError: Error?) +} + +extension DAppInteractionError: ErrorContentConvertible { + func toErrorContent(for locale: Locale?) -> ErrorContent { + let title: String + let message: String + + switch self { + case let .unexpected(reason, _): + title = R.string.localizable.commonErrorGeneralTitle(preferredLanguages: locale?.rLanguages) + message = R.string.localizable.dappUnexpectedErrorFormat( + reason, + preferredLanguages: locale?.rLanguages + ) + } + + return ErrorContent(title: title, message: message) + } } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift index a92e075691..614804bd11 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionFactory.swift @@ -1,10 +1,14 @@ import Foundation +import SoraFoundation final class DAppInteractionFactory { static func createMediator() -> DAppInteractionMediating { let logger = Logger.shared - let presenter = DAppInteractionPresenter(logger: logger) + let presenter = DAppInteractionPresenter( + logger: logger, + localizationManager: LocalizationManager.shared + ) let storageFacade = UserDataStorageFacade.shared let accountRepositoryFactory = AccountRepositoryFactory(storageFacade: storageFacade) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift index ff9b28e760..1eb6a01aef 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionMediator.swift @@ -55,17 +55,18 @@ final class DAppInteractionMediator { transport?.process(message: queueMessage.underliningMessage, host: queueMessage.host) } - private func bringPhishingDetectedStateAndNotify(for host: String) { - let allNotPhishing = transports + private func bringPhishingDetectedStateAndNotify(for host: String, transportName: String) { + let shouldNotNotifyPhishing = transports + .filter { $0.name == transportName } .map { $0.bringPhishingDetectedStateIfNeeded() } .allSatisfy { !$0 } - if !allNotPhishing { + if !shouldNotNotifyPhishing { presenter.didDetectPhishing(host: host) } } - private func verifyPhishing(for host: String?, completion: ((Bool) -> Void)?) { + private func verifyPhishing(for host: String?, transportName: String, completion: ((Bool) -> Void)?) { guard let host = host else { completion?(true) return @@ -75,12 +76,14 @@ final class DAppInteractionMediator { switch result { case let .success(isNotPhishing): if !isNotPhishing { - self?.bringPhishingDetectedStateAndNotify(for: host) + self?.bringPhishingDetectedStateAndNotify(for: host, transportName: transportName) } completion?(isNotPhishing) case let .failure(error): - self?.presenter.didReceive(error: .phishingVerifierFailed(error)) + self?.presenter.didReceive( + error: .unexpected(reason: "phishing detection failed", internalError: error) + ) } } } @@ -88,7 +91,7 @@ final class DAppInteractionMediator { extension DAppInteractionMediator: DAppInteractionMediating { func register(transport: DAppTransportProtocol) { - guard !transports.contains(where: { $0 !== transport }) else { + guard !transports.contains(where: { $0 === transport }) else { return } @@ -107,7 +110,7 @@ extension DAppInteractionMediator: DAppInteractionMediating { securedLayer.scheduleExecutionIfAuthorized { [weak self] in self?.logger?.debug("Did receive \(name) message from \(host ?? ""): \(message)") - self?.verifyPhishing(for: host) { [weak self] isNotPhishing in + self?.verifyPhishing(for: host, transportName: name) { [weak self] isNotPhishing in if isNotPhishing { let queueMessage = QueueMessage( host: host, @@ -153,6 +156,10 @@ extension DAppInteractionMediator: DAppInteractionInputProtocol { self?.transports.first(where: { $0.name == name })?.processAuth(response: response) } } + + func completePhishingStateHandling() { + children.forEach { $0.completePhishingStateHandling() } + } } extension DAppInteractionMediator: ChainsStoreDelegate { diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift index c88899c256..df4515fe05 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionPresenter.swift @@ -1,15 +1,18 @@ import UIKit import SoraUI +import SoraFoundation -final class DAppInteractionPresenter { +final class DAppInteractionPresenter: AlertPresentable, ErrorPresentable { var window: UIWindow? { UIApplication.shared.keyWindow } weak var interactor: DAppInteractionInputProtocol? let logger: LoggerProtocol + let localizationManager: LocalizationManagerProtocol - init(logger: LoggerProtocol) { + init(logger: LoggerProtocol, localizationManager: LocalizationManagerProtocol) { self.logger = logger + self.localizationManager = localizationManager } private func presentModal(the controller: UIViewController, style: UIModalPresentationStyle = .overFullScreen) { @@ -114,6 +117,8 @@ extension DAppInteractionPresenter: DAppInteractionOutputProtocol { func didReceive(error: DAppInteractionError) { logger.error("Did receive error: \(error)") + + _ = present(error: error, from: nil, locale: localizationManager.selectedLocale) } } @@ -134,6 +139,6 @@ extension DAppInteractionPresenter: DAppOperationConfirmDelegate { extension DAppInteractionPresenter: DAppPhishingViewDelegate { func dappPhishingViewDidHide() { - // TODO: we might need to notify child ui to hide phishing interface + interactor?.completePhishingStateHandling() } } diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift index cfd98306b1..6337bd5efd 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionProtocols.swift @@ -34,6 +34,7 @@ protocol DAppInteractionMediating: AnyObject, ApplicationServiceProtocol { protocol DAppInteractionInputProtocol: AnyObject { func processConfirmation(response: DAppOperationResponse, forTransport name: String) func processAuth(response: DAppAuthResponse, forTransport name: String) + func completePhishingStateHandling() } protocol DAppInteractionOutputProtocol: AnyObject { @@ -45,4 +46,6 @@ protocol DAppInteractionOutputProtocol: AnyObject { protocol DAppInteractionChildProtocol: ApplicationServiceProtocol { var mediator: DAppInteractionMediating? { get set } + + func completePhishingStateHandling() } diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift index 4482d5e775..8d53e8d1e3 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -111,4 +111,8 @@ extension WalletConnectInteractor: DAppInteractionChildProtocol { func throttle() { mediator?.unregister(transport: transport) } + + func completePhishingStateHandling() { + // do nothing as wallet connect continues working after phishing detected + } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index ae62b3d11b..c8e8ed2078 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -100,6 +100,17 @@ final class WalletConnectTransport { } } } + + private func checkStateTransition( + from oldState: WalletConnectStateProtocol?, + to newState: WalletConnectStateProtocol + ) { + let prevCanHandleMessage = oldState?.canHandleMessage() ?? false + + if !prevCanHandleMessage, newState.canHandleMessage() { + delegate?.walletConnectAskNextMessage(transport: self) + } + } } extension WalletConnectTransport: WalletConnectTransportProtocol { @@ -162,7 +173,7 @@ extension WalletConnectTransport { } func bringPhishingDetectedStateIfNeeded() -> Bool { - // TODO: Handle phishing transition + // just notify user but remain in the same state true } @@ -203,23 +214,23 @@ extension WalletConnectTransport { extension WalletConnectTransport: WalletConnectStateMachineProtocol { func emit(nextState: WalletConnectStateProtocol) { - let prevCanHandleMessage = state?.canHandleMessage() ?? false - + let oldState = state state = nextState nextState.proceed(with: dataSource) - if !prevCanHandleMessage, nextState.canHandleMessage() { - delegate?.walletConnectAskNextMessage(transport: self) - } + checkStateTransition(from: oldState, to: nextState) } func emit(authRequest: DAppAuthRequest, nextState: WalletConnectStateProtocol) { + let oldState = state state = nextState delegate?.walletConnect(transport: self, authorize: authRequest) nextState.proceed(with: dataSource) + + checkStateTransition(from: oldState, to: nextState) } func emit( @@ -227,35 +238,47 @@ extension WalletConnectTransport: WalletConnectStateMachineProtocol { type: DAppSigningType, nextState: WalletConnectStateProtocol ) { + let oldState = state state = nextState delegate?.walletConnect(transport: self, sign: signingRequest, type: type) nextState.proceed(with: dataSource) + + checkStateTransition(from: oldState, to: nextState) } func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) { + let oldState = state state = nextState service.submit(proposalDecision: proposalDecision) nextState.proceed(with: dataSource) + + checkStateTransition(from: oldState, to: nextState) } func emit(signDecision: WalletConnectSignDecision, nextState: WalletConnectStateProtocol) { + let oldState = state state = nextState service.submit(signingDecision: signDecision) nextState.proceed(with: dataSource) + + checkStateTransition(from: oldState, to: nextState) } func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) { + let oldState = state state = nextState delegate?.walletConnect(transport: self, didFail: .stateFailed(error)) nextState.proceed(with: dataSource) + + checkStateTransition(from: oldState, to: nextState) } } From 47706f54e89ef874494ae17dc493eb251e7177a2 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 17:04:23 +0500 Subject: [PATCH 45/54] error handling add --- novawallet.xcodeproj/project.pbxproj | 12 +- .../WalletConnect/WalletConnectService.swift | 99 +++++++----- .../Model/WalletConnectSignModelFactory.swift | 33 ++-- .../WalletConnectErrorPresentable.swift | 51 +++++++ .../Service/WalletConnectInteractor.swift | 25 ++- .../Service/WalletConnectPresenter.swift | 22 ++- .../Service/WalletConnectProtocols.swift | 6 +- .../Service/WalletConnectServiceFactory.swift | 9 +- ... => WCSessionDetailsInteractorError.swift} | 2 +- ...WalletConnectSessionDetailsPresenter.swift | 9 +- ...WalletConnectSessionDetailsProtocols.swift | 4 +- .../WalletConnectSessionsInteractor.swift | 6 +- ...WalletConnectSessionsInteractorError.swift | 1 + .../WalletConnectSessionsPresenter.swift | 2 + .../WalletConnectSessionsProtocols.swift | 2 +- .../WalletConnectStateAuthorizing.swift | 24 ++- .../States/WalletConnectStateError.swift | 23 +++ .../States/WalletConnectStateNewMessage.swift | 26 ++-- .../States/WalletConnectStateProtocols.swift | 12 +- .../States/WalletConnectStateSigning.swift | 5 +- .../Transport/WalletConnectTransport.swift | 47 ++++-- .../WalletConnectTransportError.swift | 3 +- .../Modules/Settings/SettingsInteractor.swift | 6 +- .../Modules/Settings/SettingsPresenter.swift | 6 + .../Modules/Settings/SettingsProtocols.swift | 3 +- novawallet/en.lproj/Localizable.strings | 4 + novawallet/ru.lproj/Localizable.strings | 4 + novawalletTests/Mocks/ModuleMocks.swift | 144 +++++++++++++++--- .../DAppOperationConfirmTests.swift | 6 +- .../Modules/Settings/SettingsTests.swift | 2 +- 30 files changed, 452 insertions(+), 146 deletions(-) create mode 100644 novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectErrorPresentable.swift rename novawallet/Modules/DApp/WalletConnect/SessionDetails/{WalletConnectSessionDetailsInteractorError.swift => WCSessionDetailsInteractorError.swift} (62%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 1008b032cd..a8ff5fe9ab 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1112,7 +1112,7 @@ 84592F4C298716540001BB56 /* GovernanceOffchainVotingFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F4B298716540001BB56 /* GovernanceOffchainVotingFactory.swift */; }; 84592F4F298716E80001BB56 /* GovernanceOffchainVoting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F4E298716E80001BB56 /* GovernanceOffchainVoting.swift */; }; 84592F512987B2E80001BB56 /* SubqueryVotingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84592F502987B2E80001BB56 /* SubqueryVotingResponse.swift */; }; - 845938842A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */; }; + 845938842A03D34F00292BFF /* WCSessionDetailsInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845938832A03D34F00292BFF /* WCSessionDetailsInteractorError.swift */; }; 8459A9C827469E4B000D6278 /* AcalaContributionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */; }; 8459A9CA2746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9C92746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift */; }; 8459A9CC2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459A9CB2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift */; }; @@ -1448,6 +1448,7 @@ 847A6C0B28817E4000477F77 /* AssetListBaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847A6C0A28817E4000477F77 /* AssetListBaseInteractor.swift */; }; 847ABE3128532E1B00851218 /* ConsesusType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847ABE3028532E1B00851218 /* ConsesusType.swift */; }; 847ABE332853333A00851218 /* StakingSharedState+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847ABE322853333A00851218 /* StakingSharedState+Duration.swift */; }; + 847C15BF2A0CFE0D003F3FF8 /* WalletConnectErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847C15BE2A0CFE0D003F3FF8 /* WalletConnectErrorPresentable.swift */; }; 847C9620255340F2002D288F /* ExportGenericViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847C961F255340F2002D288F /* ExportGenericViewController.swift */; }; 847C962825534134002D288F /* ExportGenericProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847C962725534134002D288F /* ExportGenericProtocols.swift */; }; 847C96302553426D002D288F /* ExportGenericViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847C962F2553426D002D288F /* ExportGenericViewModel.swift */; }; @@ -4607,7 +4608,7 @@ 84592F4B298716540001BB56 /* GovernanceOffchainVotingFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainVotingFactory.swift; sourceTree = ""; }; 84592F4E298716E80001BB56 /* GovernanceOffchainVoting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceOffchainVoting.swift; sourceTree = ""; }; 84592F502987B2E80001BB56 /* SubqueryVotingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubqueryVotingResponse.swift; sourceTree = ""; }; - 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsInteractorError.swift; sourceTree = ""; }; + 845938832A03D34F00292BFF /* WCSessionDetailsInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WCSessionDetailsInteractorError.swift; sourceTree = ""; }; 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcalaContributionSource.swift; sourceTree = ""; }; 8459A9C92746A1BC000D6278 /* CrowdloanOffchainSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanOffchainSubscriber.swift; sourceTree = ""; }; 8459A9CB2746A1E9000D6278 /* CrowdloanOffchainSubscriptionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdloanOffchainSubscriptionHandler.swift; sourceTree = ""; }; @@ -4946,6 +4947,7 @@ 847A6C0A28817E4000477F77 /* AssetListBaseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListBaseInteractor.swift; sourceTree = ""; }; 847ABE3028532E1B00851218 /* ConsesusType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsesusType.swift; sourceTree = ""; }; 847ABE322853333A00851218 /* StakingSharedState+Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StakingSharedState+Duration.swift"; sourceTree = ""; }; + 847C15BE2A0CFE0D003F3FF8 /* WalletConnectErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectErrorPresentable.swift; sourceTree = ""; }; 847C961F255340F2002D288F /* ExportGenericViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportGenericViewController.swift; sourceTree = ""; }; 847C962725534134002D288F /* ExportGenericProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportGenericProtocols.swift; sourceTree = ""; }; 847C962F2553426D002D288F /* ExportGenericViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportGenericViewModel.swift; sourceTree = ""; }; @@ -7994,7 +7996,7 @@ FC122CEA0D33584669126731 /* WalletConnectSessionDetailsViewController.swift */, BA4D4C049E3B32A529DDFEB5 /* WalletConnectSessionDetailsViewLayout.swift */, BE103341935B2A4B8C32B966 /* WalletConnectSessionDetailsViewFactory.swift */, - 845938832A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift */, + 845938832A03D34F00292BFF /* WCSessionDetailsInteractorError.swift */, ); path = SessionDetails; sourceTree = ""; @@ -8206,6 +8208,7 @@ isa = PBXGroup; children = ( 8413C4A82A025AC3001E190A /* WalletConnectScanPresentable.swift */, + 847C15BE2A0CFE0D003F3FF8 /* WalletConnectErrorPresentable.swift */, ); path = Protocols; sourceTree = ""; @@ -20057,7 +20060,7 @@ 879D493C025963619CFADF4F /* GovernanceUnlockSetupProtocols.swift in Sources */, 46298240F3528B5C62AEC29E /* GovernanceUnlockSetupWireframe.swift in Sources */, 9097EE6D11E2E023D2637BE5 /* GovernanceUnlockSetupPresenter.swift in Sources */, - 845938842A03D34F00292BFF /* WalletConnectSessionDetailsInteractorError.swift in Sources */, + 845938842A03D34F00292BFF /* WCSessionDetailsInteractorError.swift in Sources */, 38D0977931828C7894579968 /* GovernanceUnlockSetupInteractor.swift in Sources */, 16098DABB1C9C058C1965F1D /* GovernanceUnlockSetupViewController.swift in Sources */, 8493FF38291A59D800F09F1B /* ReferendumMetadataMapper.swift in Sources */, @@ -20184,6 +20187,7 @@ 12F8A8A869D05D66B658F649 /* GovernanceDelegateSetupWireframe.swift in Sources */, 061C16B970BE568D7A80F53A /* GovernanceDelegateSetupPresenter.swift in Sources */, 7D690E88EAB7437B9F69C92F /* GovernanceDelegateSetupInteractor.swift in Sources */, + 847C15BF2A0CFE0D003F3FF8 /* WalletConnectErrorPresentable.swift in Sources */, B8DC27C3CAE9846A5E56B4EE /* GovernanceDelegateSetupViewController.swift in Sources */, 4C4142B4CB2DBCA1F06DC046 /* GovernanceDelegateSetupViewLayout.swift in Sources */, 6789ED94EFF73E5B47956462 /* GovernanceDelegateSetupViewFactory.swift in Sources */, diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index 9f38dd9ab4..0553f41c06 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -7,30 +7,21 @@ protocol WalletConnectServiceDelegate: AnyObject { func walletConnect(service: WalletConnectServiceProtocol, proposal: Session.Proposal) func walletConnect(service: WalletConnectServiceProtocol, didChange sessions: [Session]) func walletConnect(service: WalletConnectServiceProtocol, request: Request, session: Session?) - func walletConnect(service: WalletConnectServiceProtocol, error: WalletConnectServiceError) } protocol WalletConnectServiceProtocol: ApplicationServiceProtocol, AnyObject { var delegate: WalletConnectServiceDelegate? { get set } - func connect(uri: String) + func connect(uri: String, completion: @escaping (Error?) -> Void) - func submit(proposalDecision: WalletConnectProposalDecision) - func submit(signingDecision: WalletConnectSignDecision) + func submit(proposalDecision: WalletConnectProposalDecision, completion: @escaping (Error?) -> Void) + func submit(signingDecision: WalletConnectSignDecision, completion: @escaping (Error?) -> Void) func getSessions() -> [Session] func disconnect(from session: String, completion: @escaping (Error?) -> Void) } -enum WalletConnectServiceError: Error { - case setupNeeded - case connectFailed(uri: String, internalError: Error) - case proposalFailed(decision: WalletConnectProposalDecision, internalError: Error) - case signFailed(decision: WalletConnectSignDecision, internalError: Error) - case disconnectionFailed(sessionId: String, internalError: Error) -} - final class WalletConnectService { private var networking: NetworkingInteractor? @Atomic(defaultValue: nil) private var pairing: PairingClient? @@ -160,17 +151,17 @@ final class WalletConnectService { networking = nil } - private func notify(error: WalletConnectServiceError) { + private func notify(completion: @escaping (Error?) -> Void, error: Error?) { DispatchQueue.main.async { - self.delegate?.walletConnect(service: self, error: error) + completion(error) } } } extension WalletConnectService: WalletConnectServiceProtocol { - func connect(uri: String) { + func connect(uri: String, completion: @escaping (Error?) -> Void) { guard let pairingUri = WalletConnectURI(string: uri), let pairing = pairing else { - notify(error: .setupNeeded) + notify(completion: completion, error: CommonError.dataCorruption) return } @@ -178,16 +169,17 @@ extension WalletConnectService: WalletConnectServiceProtocol { do { try await pairing.pair(uri: pairingUri) self?.logger.debug("Pairing submitted: \(uri)") + self?.notify(completion: completion, error: nil) } catch { self?.logger.error("Pairing failed \(uri): \(error)") - self?.notify(error: .connectFailed(uri: uri, internalError: error)) + self?.notify(completion: completion, error: error) } } } - func submit(proposalDecision: WalletConnectProposalDecision) { + func submit(proposalDecision: WalletConnectProposalDecision, completion: @escaping (Error?) -> Void) { guard let client = client else { - notify(error: .setupNeeded) + notify(completion: completion, error: CommonError.undefined) return } @@ -199,16 +191,18 @@ extension WalletConnectService: WalletConnectServiceProtocol { case let .reject(proposal): try await client.reject(proposalId: proposal.id, reason: .userRejected) } + + self?.notify(completion: completion, error: nil) } catch { self?.logger.error("Decision submission failed: \(error)") - self?.notify(error: .proposalFailed(decision: proposalDecision, internalError: error)) + self?.notify(completion: completion, error: error) } } } - func submit(signingDecision: WalletConnectSignDecision) { + func submit(signingDecision: WalletConnectSignDecision, completion: @escaping (Error?) -> Void) { guard let client = client else { - notify(error: .setupNeeded) + notify(completion: completion, error: CommonError.undefined) return } @@ -219,9 +213,11 @@ extension WalletConnectService: WalletConnectServiceProtocol { requestId: signingDecision.request.id, response: signingDecision.result ) + + notify(completion: completion, error: nil) } catch { self?.logger.error("Signature submission failed: \(error)") - self?.notify(error: .signFailed(decision: signingDecision, internalError: error)) + notify(completion: completion, error: error) } } } @@ -249,32 +245,36 @@ extension WalletConnectService: WalletConnectServiceProtocol { } func disconnect(from session: String, completion: @escaping (Error?) -> Void) { - Task { + Task { [weak self] in do { try await client?.disconnect(topic: session) - DispatchQueue.main.async { - completion(nil) - } + self?.notify(completion: completion, error: nil) } catch { logger.error("Disconnecting \(session) failed: \(error)") - notify(error: .disconnectionFailed(sessionId: session, internalError: error)) - - DispatchQueue.main.async { - completion(error) - } + self?.notify(completion: completion, error: error) } } } } -private final class DefaultWebSocket: WebSocket, WebSocketConnecting { +private final class DefaultWebSocket: WebSocketConnecting { public var isConnected: Bool { connected } + public var request: URLRequest { + get { + webSocket.request + } + + set { + webSocket.request = newValue + } + } + @Atomic(defaultValue: false) private var connected: Bool public var onConnect: (() -> Void)? @@ -283,10 +283,12 @@ private final class DefaultWebSocket: WebSocket, WebSocketConnecting { public var onText: ((String) -> Void)? - override init(request: URLRequest, engine: Engine) { - super.init(request: request, engine: engine) + private let webSocket: WebSocket - onEvent = { [weak self] event in + init(request: URLRequest, engine: Engine) { + webSocket = WebSocket(request: request, engine: engine) + + webSocket.onEvent = { [weak self] event in switch event { case .connected: self?.connected = true @@ -307,6 +309,22 @@ private final class DefaultWebSocket: WebSocket, WebSocketConnecting { } } } + + func connect() { + webSocket.connect() + } + + func disconnect() { + connected = false + + webSocket.forceDisconnect() + + onDisconnect?(nil) + } + + func write(string: String, completion: (() -> Void)?) { + webSocket.write(string: string, completion: completion) + } } private struct DefaultSocketFactory: WebSocketFactory { @@ -315,6 +333,13 @@ private struct DefaultSocketFactory: WebSocketFactory { // This is specifics of Starscream due to how Origin is set urlRequest.addValue("allowed.domain.com", forHTTPHeaderField: "Origin") - return DefaultWebSocket(request: urlRequest) + + let engine = WSEngine( + transport: TCPTransport(), + certPinner: FoundationSecurity(), + compressionHandler: nil + ) + + return DefaultWebSocket(request: urlRequest, engine: engine) } } diff --git a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift index 2f805849d1..f21f9e188a 100644 --- a/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Model/WalletConnectSignModelFactory.swift @@ -14,7 +14,8 @@ enum WalletConnectSignModelFactory { private static func parseAndValidatePolkadotParams( for wallet: MetaAccountModel, chain: ChainModel, - params: AnyCodable + params: AnyCodable, + method: WalletConnectMethod ) throws -> JSON { guard let walletAccountId = wallet.fetch(for: chain.accountRequest())?.accountId else { throw WalletConnectSignModelFactoryError.missingAccount(chainId: chain.chainId) @@ -28,7 +29,7 @@ enum WalletConnectSignModelFactory { ) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, - method: .polkadotSignTransaction + method: method ) } @@ -45,18 +46,20 @@ enum WalletConnectSignModelFactory { private static func createPolkadotSignTransaction( for wallet: MetaAccountModel, chain: ChainModel, - params: AnyCodable + params: AnyCodable, + method: WalletConnectMethod ) throws -> JSON { let json = try parseAndValidatePolkadotParams( for: wallet, chain: chain, - params: params + params: params, + method: method ) guard let payload = try json.transactionPayload?.map(to: PolkadotExtensionExtrinsic.self) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, - method: .polkadotSignTransaction + method: method ) } @@ -66,25 +69,27 @@ enum WalletConnectSignModelFactory { private static func createPolkadotSignMessage( for wallet: MetaAccountModel, chain: ChainModel, - params: AnyCodable + params: AnyCodable, + method: WalletConnectMethod ) throws -> JSON { let json = try parseAndValidatePolkadotParams( for: wallet, chain: chain, - params: params + params: params, + method: method ) guard let messageJson = json.message, messageJson.stringValue != nil else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, - method: .polkadotSignTransaction + method: method ) } return messageJson } - private static func createEthereumTransaction(for params: AnyCodable) throws -> JSON { + private static func createEthereumTransaction(for params: AnyCodable, method: WalletConnectMethod) throws -> JSON { let json = try params.get(JSON.self) guard @@ -93,7 +98,7 @@ enum WalletConnectSignModelFactory { ) else { throw WalletConnectSignModelFactoryError.invalidParams( params: json, - method: .polkadotSignTransaction + method: method ) } @@ -217,16 +222,18 @@ extension WalletConnectSignModelFactory { return try createPolkadotSignTransaction( for: wallet, chain: chain, - params: params + params: params, + method: method ) case .polkadotSignMessage: return try createPolkadotSignMessage( for: wallet, chain: chain, - params: params + params: params, + method: method ) case .ethSignTransaction, .ethSendTransaction: - return try createEthereumTransaction(for: params) + return try createEthereumTransaction(for: params, method: method) case .ethPersonalSign: return try createPersonalSignMessage( for: wallet, diff --git a/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectErrorPresentable.swift b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectErrorPresentable.swift new file mode 100644 index 0000000000..8ab54bf609 --- /dev/null +++ b/novawallet/Modules/DApp/WalletConnect/Protocols/WalletConnectErrorPresentable.swift @@ -0,0 +1,51 @@ +import Foundation + +protocol WalletConnectErrorPresentable { + func presentWCConnectionError(from view: ControllerBackedProtocol?, locale: Locale?) + func presentWCDisconnectionError(from view: ControllerBackedProtocol?, locale: Locale?) + func presentWCSignatureSubmissionError(from view: ControllerBackedProtocol?, locale: Locale?) + func presentWCAuthSubmissionError(from view: ControllerBackedProtocol?, locale: Locale?) +} + +extension WalletConnectErrorPresentable where Self: AlertPresentable { + private func presentWCError(from view: ControllerBackedProtocol?, message: String, locale: Locale?) { + let title = R.string.localizable.commonWalletConnect( + preferredLanguages: locale?.rLanguages + ) + + present( + message: message, + title: title, + closeAction: R.string.localizable.commonClose(preferredLanguages: locale?.rLanguages), + from: view + ) + } + + func presentWCConnectionError(from view: ControllerBackedProtocol?, locale: Locale?) { + let message = R.string.localizable.walletConnectPairingError(preferredLanguages: locale?.rLanguages) + + presentWCError(from: view, message: message, locale: locale) + } + + func presentWCDisconnectionError(from view: ControllerBackedProtocol?, locale: Locale?) { + let message = R.string.localizable.walletConnectDisconnectError(preferredLanguages: locale?.rLanguages) + + presentWCError(from: view, message: message, locale: locale) + } + + func presentWCSignatureSubmissionError(from view: ControllerBackedProtocol?, locale: Locale?) { + let message = R.string.localizable.walletConnectSignatureSubmitError( + preferredLanguages: locale?.rLanguages + ) + + presentWCError(from: view, message: message, locale: locale) + } + + func presentWCAuthSubmissionError(from view: ControllerBackedProtocol?, locale: Locale?) { + let message = R.string.localizable.walletConnectProposalResultSubmitError( + preferredLanguages: locale?.rLanguages + ) + + presentWCError(from: view, message: message, locale: locale) + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift index 8d53e8d1e3..c8ce8b5cc2 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectInteractor.swift @@ -4,6 +4,7 @@ import WalletConnectSwiftV2 final class WalletConnectInteractor { let presenter: WalletConnectInteractorOutputProtocol let transport: WalletConnectTransportProtocol + let securedLayer: SecurityLayerServiceProtocol weak var mediator: DAppInteractionMediating? @@ -11,18 +12,24 @@ final class WalletConnectInteractor { init( transport: WalletConnectTransportProtocol, - presenter: WalletConnectInteractorOutputProtocol + presenter: WalletConnectInteractorOutputProtocol, + securedLayer: SecurityLayerServiceProtocol ) { self.transport = transport self.presenter = presenter + self.securedLayer = securedLayer } } extension WalletConnectInteractor: WalletConnectInteractorInputProtocol {} extension WalletConnectInteractor: WalletConnectDelegateInputProtocol { - func connect(uri: String) { - transport.connect(uri: uri) + func connect(uri: String, completion: @escaping (Error?) -> Void) { + transport.connect(uri: uri) { [weak self] optError in + self?.securedLayer.scheduleExecutionIfAuthorized { + completion(optError) + } + } } func add(delegate: WalletConnectDelegateOutputProtocol) { @@ -46,7 +53,11 @@ extension WalletConnectInteractor: WalletConnectDelegateInputProtocol { } func disconnect(from session: String, completion: @escaping (Error?) -> Void) { - transport.disconnect(from: session, completion: completion) + transport.disconnect(from: session) { [weak self] optError in + self?.securedLayer.scheduleExecutionIfAuthorized { + completion(optError) + } + } } } @@ -60,9 +71,11 @@ extension WalletConnectInteractor: WalletConnectTransportDelegate { func walletConnect( transport _: WalletConnectTransportProtocol, - didFail _: WalletConnectTransportError + didFail error: WalletConnectTransportError ) { - // TODO: Handle error + securedLayer.scheduleExecutionIfAuthorized { [weak self] in + self?.presenter.didReceive(error: error) + } } func walletConnect(transport _: WalletConnectTransportProtocol, authorize request: DAppAuthRequest) { diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift index bd629fab29..d371fc84f8 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectPresenter.swift @@ -1,13 +1,29 @@ import Foundation +import SoraFoundation -final class WalletConnectPresenter { +final class WalletConnectPresenter: AlertPresentable, ErrorPresentable, WalletConnectErrorPresentable { weak var interactor: WalletConnectInteractorInputProtocol? let logger: LoggerProtocol + let localizationManager: LocalizationManagerProtocol - init(logger: LoggerProtocol) { + init(logger: LoggerProtocol, localizationManager: LocalizationManagerProtocol) { self.logger = logger + self.localizationManager = localizationManager } } -extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol {} +extension WalletConnectPresenter: WalletConnectInteractorOutputProtocol { + func didReceive(error: WalletConnectTransportError) { + logger.error("Did receive error: \(error)") + + switch error { + case let .stateFailed(walletConnectStateError): + _ = present(error: walletConnectStateError, from: nil, locale: localizationManager.selectedLocale) + case .signingDecisionSubmissionFailed: + presentWCSignatureSubmissionError(from: nil, locale: localizationManager.selectedLocale) + case .proposalDecisionSubmissionFailed: + presentWCAuthSubmissionError(from: nil, locale: localizationManager.selectedLocale) + } + } +} diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift index 724a226111..2931a352a1 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectProtocols.swift @@ -1,9 +1,11 @@ protocol WalletConnectInteractorInputProtocol: AnyObject {} -protocol WalletConnectInteractorOutputProtocol: AnyObject {} +protocol WalletConnectInteractorOutputProtocol: AnyObject { + func didReceive(error: WalletConnectTransportError) +} protocol WalletConnectDelegateInputProtocol: AnyObject { - func connect(uri: String) + func connect(uri: String, completion: @escaping (Error?) -> Void) func add(delegate: WalletConnectDelegateOutputProtocol) func remove(delegate: WalletConnectDelegateOutputProtocol) diff --git a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift index c4a9d6e09a..81abed51b3 100644 --- a/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift +++ b/novawallet/Modules/DApp/WalletConnect/Service/WalletConnectServiceFactory.swift @@ -1,5 +1,6 @@ import Foundation import RobinHood +import SoraFoundation struct WalletConnectServiceFactory { static func createInteractor( @@ -25,9 +26,15 @@ struct WalletConnectServiceFactory { logger: Logger.shared ) + let presenter = WalletConnectPresenter( + logger: Logger.shared, + localizationManager: LocalizationManager.shared + ) + return .init( transport: transport, - presenter: WalletConnectPresenter(logger: Logger.shared) + presenter: presenter, + securedLayer: SecurityLayerService.shared ) } } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WCSessionDetailsInteractorError.swift similarity index 62% rename from novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift rename to novawallet/Modules/DApp/WalletConnect/SessionDetails/WCSessionDetailsInteractorError.swift index 5c6e611b10..c7ef5cd13e 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsInteractorError.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WCSessionDetailsInteractorError.swift @@ -1,6 +1,6 @@ import Foundation -enum WalletConnectSessionDetailsInteractorError: Error { +enum WCSessionDetailsInteractorError: Error { case sessionUpdateFailed(Error) case disconnectionFailed(Error) } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift index a1b02eb15e..efd15137da 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsPresenter.swift @@ -73,7 +73,7 @@ extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsInter wireframe.close(view: view) } - func didReceive(error: WalletConnectSessionDetailsInteractorError) { + func didReceive(error: WCSessionDetailsInteractorError) { logger.error("Did receive error: \(error)") switch error { @@ -87,12 +87,7 @@ extension WalletConnectSessionDetailsPresenter: WalletConnectSessionDetailsInter case .disconnectionFailed: view?.didStopLoading() - wireframe.presentRequestStatus( - on: view, - locale: selectedLocale - ) { [weak self] in - self?.performDisconnect() - } + wireframe.presentWCDisconnectionError(from: view, locale: selectedLocale) } } } diff --git a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift index 709d15ba25..ef56324044 100644 --- a/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/SessionDetails/WalletConnectSessionDetailsProtocols.swift @@ -17,11 +17,11 @@ protocol WalletConnectSessionDetailsInteractorInputProtocol: AnyObject { protocol WalletConnectSessionDetailsInteractorOutputProtocol: AnyObject { func didUpdate(session: WalletConnectSession) func didDisconnect() - func didReceive(error: WalletConnectSessionDetailsInteractorError) + func didReceive(error: WCSessionDetailsInteractorError) } protocol WalletConnectSessionDetailsWireframeProtocol: AlertPresentable, ErrorPresentable, - CommonRetryable { + CommonRetryable, WalletConnectErrorPresentable { func close(view: WalletConnectSessionDetailsViewProtocol?) func showNetworks(from view: WalletConnectSessionDetailsViewProtocol?, networks: [ChainModel]) } diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift index 79be86906d..bb7df56426 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractor.swift @@ -29,7 +29,11 @@ extension WalletConnectSessionsInteractor: WalletConnectSessionsInteractorInputP } func connect(uri: String) { - walletConnect.connect(uri: uri) + walletConnect.connect(uri: uri) { [weak self] optError in + if let error = optError { + self?.presenter?.didReceive(error: .connectionFailed(error)) + } + } } func retrySessionsFetch() { diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift index dfc28e73ff..bd90a910b8 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsInteractorError.swift @@ -2,4 +2,5 @@ import Foundation enum WalletConnectSessionsInteractorError: Error { case sessionsFetchFailed(Error) + case connectionFailed(Error) } diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift index 54d61e9cab..b47b6e6e37 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsPresenter.swift @@ -69,6 +69,8 @@ extension WalletConnectSessionsPresenter: WalletConnectSessionsInteractorOutputP wireframe.presentRequestStatus(on: view, locale: selectedLocale) { [weak self] in self?.interactor.retrySessionsFetch() } + case .connectionFailed: + wireframe.presentWCConnectionError(from: view, locale: selectedLocale) } } } diff --git a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift index c5657536ae..98ebcc544f 100644 --- a/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/Sessions/WalletConnectSessionsProtocols.swift @@ -20,7 +20,7 @@ protocol WalletConnectSessionsInteractorOutputProtocol: AnyObject { } protocol WalletConnectSessionsWireframeProtocol: WalletConnectScanPresentable, AlertPresentable, - ErrorPresentable, CommonRetryable { + ErrorPresentable, CommonRetryable, WalletConnectErrorPresentable { func showSession(from view: WalletConnectSessionsViewProtocol?, details: WalletConnectSession) func close(view: WalletConnectSessionsViewProtocol?) } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift index 3044237181..5bced6f32b 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateAuthorizing.swift @@ -83,30 +83,38 @@ extension WalletConnectStateAuthorizing: WalletConnectStateProtocol { pairingId: proposal.pairingTopic, dataSource: dataSource ) { [weak self] optError in - guard let proposal = self?.proposal, let resolution = self?.resolution else { + guard let self = self else { return } guard optError == nil else { - // TODO: Also notify error - stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) + stateMachine.emit( + proposalDecision: .reject(proposal: self.proposal), + nextState: nextState, + error: .unexpectedData(details: "session save failed", self) + ) return } guard response.approved else { - stateMachine.emit(proposalDecision: .reject(proposal: proposal), nextState: nextState) + stateMachine.emit( + proposalDecision: .reject(proposal: self.proposal), + nextState: nextState, + error: nil + ) return } let namespaces = WalletConnectModelFactory.createSessionNamespaces( - from: proposal, + from: self.proposal, wallet: response.wallet, - resolvedChains: resolution.allResolvedChains().resolved + resolvedChains: self.resolution.allResolvedChains().resolved ) stateMachine.emit( - proposalDecision: .approve(proposal: proposal, namespaces: namespaces), - nextState: nextState + proposalDecision: .approve(proposal: self.proposal, namespaces: namespaces), + nextState: nextState, + error: nil ) } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift index 9e2a2fb089..2207bb633e 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateError.swift @@ -2,4 +2,27 @@ import Foundation enum WalletConnectStateError: Error { case unexpectedMessage(Any, WalletConnectStateProtocol) + case unexpectedData(details: String, WalletConnectStateProtocol) +} + +extension WalletConnectStateError: ErrorContentConvertible { + func toErrorContent(for locale: Locale?) -> ErrorContent { + let title = R.string.localizable.commonWalletConnect(preferredLanguages: locale?.rLanguages) + let message: String + + switch self { + case let .unexpectedData(details, _): + message = R.string.localizable.dappUnexpectedErrorFormat( + details, + preferredLanguages: locale?.rLanguages + ) + case .unexpectedMessage: + message = R.string.localizable.dappUnexpectedErrorFormat( + "unexpected message received", + preferredLanguages: locale?.rLanguages + ) + } + + return ErrorContent(title: title, message: message) + } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index 303691ab27..ffe2962c47 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -61,16 +61,19 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { stateMachine.emit(authRequest: authRequest, nextState: nextState) } - private func rejectRequest(request: Request) { + private func rejectRequest(request: Request, reason: String?) { guard let stateMachine = stateMachine else { return } - let desicion = WalletConnectSignDecision.reject(request: request) + let decision = WalletConnectSignDecision.reject(request: request) + + let error = reason.map { WalletConnectStateError.unexpectedData(details: $0, self) } stateMachine.emit( - signDecision: desicion, - nextState: WalletConnectStateReady(stateMachine: stateMachine) + signDecision: decision, + nextState: WalletConnectStateReady(stateMachine: stateMachine), + error: error ) } @@ -125,7 +128,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { } guard let method = WalletConnectMethod(rawValue: request.method) else { - rejectRequest(request: request) + rejectRequest(request: request, reason: "unsupported method: \(request.method)") return } @@ -134,13 +137,13 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { for: request.chainId, chainsStore: chainsStore ) else { - rejectRequest(request: request) + rejectRequest(request: request, reason: "unsupported chain id: \(request.chainId)") return } guard let accountId = wallet.fetch(for: chain.accountRequest())?.accountId else { - rejectRequest(request: request) + rejectRequest(request: request, reason: "missing account for chain: \(chain.chainId)") return } @@ -176,9 +179,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { nextState: nextState ) } catch { - // TODO: Handle error - - rejectRequest(request: request) + rejectRequest(request: request, reason: "signing failed") } } } @@ -204,7 +205,7 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { process(proposal: proposal, dataSource: dataSource) case let .request(request, session): guard let session = session else { - // TODO: No session found error + rejectRequest(request: request, reason: "missing session for request") return } @@ -217,8 +218,7 @@ extension WalletConnectStateNewMessage: WalletConnectStateProtocol { chainsStore: dataSource.chainsStore ) } else { - // TODO: Handle not authorized request - self?.rejectRequest(request: request) + self?.rejectRequest(request: request, reason: "missing wallet for session") } } } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift index 2544747863..35b0a82698 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateProtocols.swift @@ -9,8 +9,16 @@ protocol WalletConnectStateMachineProtocol: AnyObject { type: DAppSigningType, nextState: WalletConnectStateProtocol ) - func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) - func emit(signDecision: WalletConnectSignDecision, nextState: WalletConnectStateProtocol) + func emit( + proposalDecision: WalletConnectProposalDecision, + nextState: WalletConnectStateProtocol, + error: WalletConnectStateError? + ) + func emit( + signDecision: WalletConnectSignDecision, + nextState: WalletConnectStateProtocol, + error: WalletConnectStateError? + ) func emit(error: WalletConnectStateError, nextState: WalletConnectStateProtocol) } diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift index ee46dfbfa4..c849a9e661 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateSigning.swift @@ -38,10 +38,11 @@ extension WalletConnectStateSigning: WalletConnectStateProtocol { stateMachine.emit( signDecision: .approve(request: request, signature: result), - nextState: nextState + nextState: nextState, + error: nil ) } else { - stateMachine.emit(signDecision: .reject(request: request), nextState: nextState) + stateMachine.emit(signDecision: .reject(request: request), nextState: nextState, error: nil) } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift index c8e8ed2078..cdf4558872 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransport.swift @@ -5,7 +5,7 @@ import RobinHood protocol WalletConnectTransportProtocol: DAppTransportProtocol { var delegate: WalletConnectTransportDelegate? { get set } - func connect(uri: String) + func connect(uri: String, completion: @escaping (Error?) -> Void) func getSessionsCount() -> Int @@ -114,8 +114,8 @@ final class WalletConnectTransport { } extension WalletConnectTransport: WalletConnectTransportProtocol { - func connect(uri: String) { - service.connect(uri: uri) + func connect(uri: String, completion: @escaping (Error?) -> Void) { + service.connect(uri: uri, completion: completion) } func getSessionsCount() -> Int { @@ -248,22 +248,49 @@ extension WalletConnectTransport: WalletConnectStateMachineProtocol { checkStateTransition(from: oldState, to: nextState) } - func emit(proposalDecision: WalletConnectProposalDecision, nextState: WalletConnectStateProtocol) { + func emit( + proposalDecision: WalletConnectProposalDecision, + nextState: WalletConnectStateProtocol, + error: WalletConnectStateError? + ) { let oldState = state state = nextState - service.submit(proposalDecision: proposalDecision) + service.submit(proposalDecision: proposalDecision) { [weak self] optError in + if let error = optError, let self = self { + self.delegate?.walletConnect( + transport: self, + didFail: .proposalDecisionSubmissionFailed(error) + ) + } + } + + if let error = error { + delegate?.walletConnect(transport: self, didFail: .stateFailed(error)) + } nextState.proceed(with: dataSource) checkStateTransition(from: oldState, to: nextState) } - func emit(signDecision: WalletConnectSignDecision, nextState: WalletConnectStateProtocol) { + func emit( + signDecision: WalletConnectSignDecision, + nextState: WalletConnectStateProtocol, + error: WalletConnectStateError? + ) { let oldState = state state = nextState - service.submit(signingDecision: signDecision) + service.submit(signingDecision: signDecision) { [weak self] optError in + if let error = optError, let self = self { + self.delegate?.walletConnect(transport: self, didFail: .signingDecisionSubmissionFailed(error)) + } + } + + if let error = error { + delegate?.walletConnect(transport: self, didFail: .stateFailed(error)) + } nextState.proceed(with: dataSource) @@ -300,10 +327,4 @@ extension WalletConnectTransport: WalletConnectServiceDelegate { delegate?.walletConnect(transport: self, didReceive: .request(request, session)) } - - func walletConnect(service _: WalletConnectServiceProtocol, error: WalletConnectServiceError) { - logger.error("Error: \(error)") - - delegate?.walletConnect(transport: self, didFail: .serviceFailed(error)) - } } diff --git a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift index be6f733f94..d295a11b5f 100644 --- a/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift +++ b/novawallet/Modules/DApp/WalletConnect/Transport/WalletConnectTransportError.swift @@ -2,5 +2,6 @@ import Foundation enum WalletConnectTransportError: Error { case stateFailed(WalletConnectStateError) - case serviceFailed(WalletConnectServiceError) + case signingDecisionSubmissionFailed(Error) + case proposalDecisionSubmissionFailed(Error) } diff --git a/novawallet/Modules/Settings/SettingsInteractor.swift b/novawallet/Modules/Settings/SettingsInteractor.swift index 1249ce9bf5..7eb4a9a12c 100644 --- a/novawallet/Modules/Settings/SettingsInteractor.swift +++ b/novawallet/Modules/Settings/SettingsInteractor.swift @@ -54,7 +54,11 @@ extension SettingsInteractor: SettingsInteractorInputProtocol { } func connectWalletConnect(uri: String) { - walletConnect.connect(uri: uri) + walletConnect.connect(uri: uri) { [weak self] optError in + if let error = optError { + self?.presenter?.didFailConnection(walletConnect: error) + } + } } } diff --git a/novawallet/Modules/Settings/SettingsPresenter.swift b/novawallet/Modules/Settings/SettingsPresenter.swift index 547d315b23..05be50de06 100644 --- a/novawallet/Modules/Settings/SettingsPresenter.swift +++ b/novawallet/Modules/Settings/SettingsPresenter.swift @@ -167,6 +167,12 @@ extension SettingsPresenter: SettingsInteractorOutputProtocol { updateView() } + + func didFailConnection(walletConnect error: Error) { + logger?.error("Did receive wc error: \(error)") + + wireframe.presentWCConnectionError(from: view, locale: selectedLocale) + } } extension SettingsPresenter: URIScanDelegate { diff --git a/novawallet/Modules/Settings/SettingsProtocols.swift b/novawallet/Modules/Settings/SettingsProtocols.swift index abb7ae44da..23c41e2d2b 100644 --- a/novawallet/Modules/Settings/SettingsProtocols.swift +++ b/novawallet/Modules/Settings/SettingsProtocols.swift @@ -24,10 +24,11 @@ protocol SettingsInteractorOutputProtocol: AnyObject { func didReceiveUserDataProvider(error: Error) func didReceive(currencyCode: String) func didReceiveWalletConnect(sessionsCount: Int) + func didFailConnection(walletConnect error: Error) } protocol SettingsWireframeProtocol: ErrorPresentable, AlertPresentable, WebPresentable, ModalAlertPresenting, - EmailPresentable, WalletSwitchPresentable, WalletConnectScanPresentable { + EmailPresentable, WalletSwitchPresentable, WalletConnectScanPresentable, WalletConnectErrorPresentable { func showAccountDetails(for walletId: String, from view: ControllerBackedProtocol?) func showAccountSelection(from view: ControllerBackedProtocol?) func showLanguageSelection(from view: ControllerBackedProtocol?) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index cb3cf377f4..5a84db345d 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1222,3 +1222,7 @@ "dapps.required.networks" = "Required"; "dapps.optional.networks" = "Optional"; "dapps.request.sign.title" = "Sign Request"; +"wallet.connect.pairing.error" = "Could not connect to the dapp due to the network issues"; +"wallet.connect.disconnect.error" = "Could not close session with the dapp due to the network issues"; +"wallet.connect.signature.submit.error" = "Could not submit signature to the dapp due to the network issues"; +"wallet.connect.proposal.result.submit.error" = "Could not authorize the dapp due to the network issues"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 051260974d..0008c6371a 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1222,3 +1222,7 @@ "dapps.required.networks" = "Обязательные"; "dapps.optional.networks" = "Не обязательные"; "dapps.request.sign.title" = "Подпишите запрос"; +"wallet.connect.pairing.error" = "Не удалось подключиться к dapp из-за проблем с сетью"; +"wallet.connect.disconnect.error" = "Не удалось завершить сессию с dapp из-за проблем с сетью"; +"wallet.connect.signature.submit.error" = "Не удалось отправить подпись dapp из-за проблем с сетью"; +"wallet.connect.proposal.result.submit.error" = "Не удалось авторизовать dapp из-за проблем с сетью"; diff --git a/novawalletTests/Mocks/ModuleMocks.swift b/novawalletTests/Mocks/ModuleMocks.swift index 47a3c204e7..316319c53b 100644 --- a/novawalletTests/Mocks/ModuleMocks.swift +++ b/novawalletTests/Mocks/ModuleMocks.swift @@ -11763,16 +11763,16 @@ import SubstrateSdk - func didReceive(confimationViewModel: DAppOperationConfirmViewModel) { + func didReceive(confirmationViewModel: DAppOperationConfirmViewModel) { - return cuckoo_manager.call("didReceive(confimationViewModel: DAppOperationConfirmViewModel)", - parameters: (confimationViewModel), - escapingParameters: (confimationViewModel), + return cuckoo_manager.call("didReceive(confirmationViewModel: DAppOperationConfirmViewModel)", + parameters: (confirmationViewModel), + escapingParameters: (confirmationViewModel), superclassCall: Cuckoo.MockManager.crashOnProtocolSuperclassCall() , - defaultCall: __defaultImplStub!.didReceive(confimationViewModel: confimationViewModel)) + defaultCall: __defaultImplStub!.didReceive(confirmationViewModel: confirmationViewModel)) } @@ -11810,9 +11810,9 @@ import SubstrateSdk } - func didReceive(confimationViewModel: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(DAppOperationConfirmViewModel)> where M1.MatchedType == DAppOperationConfirmViewModel { - let matchers: [Cuckoo.ParameterMatcher<(DAppOperationConfirmViewModel)>] = [wrap(matchable: confimationViewModel) { $0 }] - return .init(stub: cuckoo_manager.createStub(for: MockDAppOperationConfirmViewProtocol.self, method: "didReceive(confimationViewModel: DAppOperationConfirmViewModel)", parameterMatchers: matchers)) + func didReceive(confirmationViewModel: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(DAppOperationConfirmViewModel)> where M1.MatchedType == DAppOperationConfirmViewModel { + let matchers: [Cuckoo.ParameterMatcher<(DAppOperationConfirmViewModel)>] = [wrap(matchable: confirmationViewModel) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockDAppOperationConfirmViewProtocol.self, method: "didReceive(confirmationViewModel: DAppOperationConfirmViewModel)", parameterMatchers: matchers)) } func didReceive(feeViewModel: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(DAppOperationFeeViewModel)> where M1.MatchedType == DAppOperationFeeViewModel { @@ -11847,9 +11847,9 @@ import SubstrateSdk @discardableResult - func didReceive(confimationViewModel: M1) -> Cuckoo.__DoNotUse<(DAppOperationConfirmViewModel), Void> where M1.MatchedType == DAppOperationConfirmViewModel { - let matchers: [Cuckoo.ParameterMatcher<(DAppOperationConfirmViewModel)>] = [wrap(matchable: confimationViewModel) { $0 }] - return cuckoo_manager.verify("didReceive(confimationViewModel: DAppOperationConfirmViewModel)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + func didReceive(confirmationViewModel: M1) -> Cuckoo.__DoNotUse<(DAppOperationConfirmViewModel), Void> where M1.MatchedType == DAppOperationConfirmViewModel { + let matchers: [Cuckoo.ParameterMatcher<(DAppOperationConfirmViewModel)>] = [wrap(matchable: confirmationViewModel) { $0 }] + return cuckoo_manager.verify("didReceive(confirmationViewModel: DAppOperationConfirmViewModel)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @discardableResult @@ -11887,7 +11887,7 @@ import SubstrateSdk - func didReceive(confimationViewModel: DAppOperationConfirmViewModel) { + func didReceive(confirmationViewModel: DAppOperationConfirmViewModel) { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -11984,6 +11984,21 @@ import SubstrateSdk } + + + func showAccountOptions() { + + return cuckoo_manager.call("showAccountOptions()", + parameters: (), + escapingParameters: (), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.showAccountOptions()) + + } + struct __StubbingProxy_DAppOperationConfirmPresenterProtocol: Cuckoo.StubbingProxy { private let cuckoo_manager: Cuckoo.MockManager @@ -12013,6 +12028,11 @@ import SubstrateSdk return .init(stub: cuckoo_manager.createStub(for: MockDAppOperationConfirmPresenterProtocol.self, method: "activateTxDetails()", parameterMatchers: matchers)) } + func showAccountOptions() -> Cuckoo.ProtocolStubNoReturnFunction<()> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return .init(stub: cuckoo_manager.createStub(for: MockDAppOperationConfirmPresenterProtocol.self, method: "showAccountOptions()", parameterMatchers: matchers)) + } + } struct __VerificationProxy_DAppOperationConfirmPresenterProtocol: Cuckoo.VerificationProxy { @@ -12053,6 +12073,12 @@ import SubstrateSdk return cuckoo_manager.verify("activateTxDetails()", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } + @discardableResult + func showAccountOptions() -> Cuckoo.__DoNotUse<(), Void> { + let matchers: [Cuckoo.ParameterMatcher] = [] + return cuckoo_manager.verify("showAccountOptions()", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + } } @@ -12086,6 +12112,12 @@ import SubstrateSdk return DefaultValueRegistry.defaultValue(for: (Void).self) } + + + func showAccountOptions() { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + } @@ -13722,6 +13754,21 @@ import Cuckoo + + + func didReceive(error: WalletConnectTransportError) { + + return cuckoo_manager.call("didReceive(error: WalletConnectTransportError)", + parameters: (error), + escapingParameters: (error), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.didReceive(error: error)) + + } + struct __StubbingProxy_WalletConnectInteractorOutputProtocol: Cuckoo.StubbingProxy { private let cuckoo_manager: Cuckoo.MockManager @@ -13731,6 +13778,11 @@ import Cuckoo } + func didReceive(error: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(WalletConnectTransportError)> where M1.MatchedType == WalletConnectTransportError { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectTransportError)>] = [wrap(matchable: error) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectInteractorOutputProtocol.self, method: "didReceive(error: WalletConnectTransportError)", parameterMatchers: matchers)) + } + } struct __VerificationProxy_WalletConnectInteractorOutputProtocol: Cuckoo.VerificationProxy { @@ -13747,6 +13799,12 @@ import Cuckoo + @discardableResult + func didReceive(error: M1) -> Cuckoo.__DoNotUse<(WalletConnectTransportError), Void> where M1.MatchedType == WalletConnectTransportError { + let matchers: [Cuckoo.ParameterMatcher<(WalletConnectTransportError)>] = [wrap(matchable: error) { $0 }] + return cuckoo_manager.verify("didReceive(error: WalletConnectTransportError)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + } } @@ -13756,6 +13814,12 @@ import Cuckoo + + + func didReceive(error: WalletConnectTransportError) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + } @@ -13785,16 +13849,16 @@ import Cuckoo - func connect(uri: String) { + func connect(uri: String, completion: @escaping (Error?) -> Void) { - return cuckoo_manager.call("connect(uri: String)", - parameters: (uri), - escapingParameters: (uri), + return cuckoo_manager.call("connect(uri: String, completion: @escaping (Error?) -> Void)", + parameters: (uri, completion), + escapingParameters: (uri, completion), superclassCall: Cuckoo.MockManager.crashOnProtocolSuperclassCall() , - defaultCall: __defaultImplStub!.connect(uri: uri)) + defaultCall: __defaultImplStub!.connect(uri: uri, completion: completion)) } @@ -13882,9 +13946,9 @@ import Cuckoo } - func connect(uri: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(String)> where M1.MatchedType == String { - let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] - return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "connect(uri: String)", parameterMatchers: matchers)) + func connect(uri: M1, completion: M2) -> Cuckoo.ProtocolStubNoReturnFunction<(String, (Error?) -> Void)> where M1.MatchedType == String, M2.MatchedType == (Error?) -> Void { + let matchers: [Cuckoo.ParameterMatcher<(String, (Error?) -> Void)>] = [wrap(matchable: uri) { $0.0 }, wrap(matchable: completion) { $0.1 }] + return .init(stub: cuckoo_manager.createStub(for: MockWalletConnectDelegateInputProtocol.self, method: "connect(uri: String, completion: @escaping (Error?) -> Void)", parameterMatchers: matchers)) } func add(delegate: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(WalletConnectDelegateOutputProtocol)> where M1.MatchedType == WalletConnectDelegateOutputProtocol { @@ -13929,9 +13993,9 @@ import Cuckoo @discardableResult - func connect(uri: M1) -> Cuckoo.__DoNotUse<(String), Void> where M1.MatchedType == String { - let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: uri) { $0 }] - return cuckoo_manager.verify("connect(uri: String)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + func connect(uri: M1, completion: M2) -> Cuckoo.__DoNotUse<(String, (Error?) -> Void), Void> where M1.MatchedType == String, M2.MatchedType == (Error?) -> Void { + let matchers: [Cuckoo.ParameterMatcher<(String, (Error?) -> Void)>] = [wrap(matchable: uri) { $0.0 }, wrap(matchable: completion) { $0.1 }] + return cuckoo_manager.verify("connect(uri: String, completion: @escaping (Error?) -> Void)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } @discardableResult @@ -13975,7 +14039,7 @@ import Cuckoo - func connect(uri: String) { + func connect(uri: String, completion: @escaping (Error?) -> Void) { return DefaultValueRegistry.defaultValue(for: (Void).self) } @@ -20257,6 +20321,21 @@ import UIKit.UIImage } + + + func didFailConnection(walletConnect error: Error) { + + return cuckoo_manager.call("didFailConnection(walletConnect: Error)", + parameters: (error), + escapingParameters: (error), + superclassCall: + + Cuckoo.MockManager.crashOnProtocolSuperclassCall() + , + defaultCall: __defaultImplStub!.didFailConnection(walletConnect: error)) + + } + struct __StubbingProxy_SettingsInteractorOutputProtocol: Cuckoo.StubbingProxy { private let cuckoo_manager: Cuckoo.MockManager @@ -20286,6 +20365,11 @@ import UIKit.UIImage return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorOutputProtocol.self, method: "didReceiveWalletConnect(sessionsCount: Int)", parameterMatchers: matchers)) } + func didFailConnection(walletConnect error: M1) -> Cuckoo.ProtocolStubNoReturnFunction<(Error)> where M1.MatchedType == Error { + let matchers: [Cuckoo.ParameterMatcher<(Error)>] = [wrap(matchable: error) { $0 }] + return .init(stub: cuckoo_manager.createStub(for: MockSettingsInteractorOutputProtocol.self, method: "didFailConnection(walletConnect: Error)", parameterMatchers: matchers)) + } + } struct __VerificationProxy_SettingsInteractorOutputProtocol: Cuckoo.VerificationProxy { @@ -20326,6 +20410,12 @@ import UIKit.UIImage return cuckoo_manager.verify("didReceiveWalletConnect(sessionsCount: Int)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } + @discardableResult + func didFailConnection(walletConnect error: M1) -> Cuckoo.__DoNotUse<(Error), Void> where M1.MatchedType == Error { + let matchers: [Cuckoo.ParameterMatcher<(Error)>] = [wrap(matchable: error) { $0 }] + return cuckoo_manager.verify("didFailConnection(walletConnect: Error)", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) + } + } } @@ -20359,6 +20449,12 @@ import UIKit.UIImage return DefaultValueRegistry.defaultValue(for: (Void).self) } + + + func didFailConnection(walletConnect error: Error) { + return DefaultValueRegistry.defaultValue(for: (Void).self) + } + } diff --git a/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift b/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift index b2a2d9585e..d212925c47 100644 --- a/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift +++ b/novawalletTests/Modules/DApps/DAppOperationConfirm/DAppOperationConfirmTests.swift @@ -131,6 +131,7 @@ class DAppOperationConfirmTests: XCTestCase { delegate: delegate, viewModelFactory: DAppOperationConfirmViewModelFactory(chain: .left(chain)), balanceViewModelFactory: balanceViewModelFactory, + chain: .left(chain), localizationManager: LocalizationManager.shared ) @@ -152,7 +153,7 @@ class DAppOperationConfirmTests: XCTestCase { } } - when(stub).didReceive(confimationViewModel: any()).then { _ in + when(stub).didReceive(confirmationViewModel: any()).then { _ in setupExpectation.fulfill() } } @@ -249,6 +250,7 @@ class DAppOperationConfirmTests: XCTestCase { delegate: delegate, viewModelFactory: DAppOperationConfirmViewModelFactory(chain: .left(chain)), balanceViewModelFactory: balanceViewModelFactory, + chain: .left(chain), localizationManager: LocalizationManager.shared ) @@ -270,7 +272,7 @@ class DAppOperationConfirmTests: XCTestCase { } } - when(stub).didReceive(confimationViewModel: any()).then { _ in + when(stub).didReceive(confirmationViewModel: any()).then { _ in setupExpectation.fulfill() } } diff --git a/novawalletTests/Modules/Settings/SettingsTests.swift b/novawalletTests/Modules/Settings/SettingsTests.swift index 2993c1319a..4b04d729ae 100644 --- a/novawalletTests/Modules/Settings/SettingsTests.swift +++ b/novawalletTests/Modules/Settings/SettingsTests.swift @@ -45,7 +45,7 @@ final class SettingsTests: XCTestCase { stub(walletConnect) { stub in when(stub).add(delegate: any()).thenDoNothing() - when(stub).connect(uri: any()).thenDoNothing() + when(stub).connect(uri: any(), completion: any()).thenDoNothing() when(stub).remove(delegate: any()).thenDoNothing() when(stub).getSessionsCount().thenReturn(0) when(stub).fetchSessions(any()).then { closure in From e1b04536d225260e8b79e8f2290904a4b99b05e9 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 17:19:56 +0500 Subject: [PATCH 46/54] address previous PRs comments --- .../Helpers/BalancesStore+Default.swift | 2 +- .../WalletConnect/WalletConnectService.swift | 16 ++++++++-------- .../StackTable/StackWalletAmountCell.swift | 2 +- .../TableViewCell/TableViewCellPosition.swift | 19 ++++++++++--------- .../ModalPicker/ModalNetworksFactory.swift | 16 ++++++++-------- .../DAppWalletAuthViewFactory.swift | 2 +- .../DAppWalletAuthWireframe.swift | 2 +- .../Choose/WalletsChooseViewFactory.swift | 2 +- .../Manage/WalletManageViewFactory.swift | 2 +- .../WalletSelectionViewFactory.swift | 2 +- 10 files changed, 33 insertions(+), 32 deletions(-) diff --git a/novawallet/Common/Helpers/BalancesStore+Default.swift b/novawallet/Common/Helpers/BalancesStore+Default.swift index 5025eb4845..e667c99db3 100644 --- a/novawallet/Common/Helpers/BalancesStore+Default.swift +++ b/novawallet/Common/Helpers/BalancesStore+Default.swift @@ -1,7 +1,7 @@ import Foundation extension BalancesStore { - static func createDefaut() -> BalancesStore? { + static func createDefault() -> BalancesStore? { guard let currencyManager = CurrencyManager.shared else { return nil } diff --git a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift index 0553f41c06..5e6725133d 100644 --- a/novawallet/Common/Services/WalletConnect/WalletConnectService.swift +++ b/novawallet/Common/Services/WalletConnect/WalletConnectService.swift @@ -51,34 +51,34 @@ final class WalletConnectService { proposalCancellable = client?.sessionProposalPublisher .receive(on: DispatchQueue.main) .sink { [weak self] proposal in - guard let strongSelf = self else { + guard let self = self else { return } - strongSelf.delegate?.walletConnect(service: strongSelf, proposal: proposal) + self.delegate?.walletConnect(service: self, proposal: proposal) } sessionCancellable = client?.sessionsPublisher .receive(on: DispatchQueue.main) .sink { [weak self] sessions in - guard let strongSelf = self else { + guard let self = self else { return } - strongSelf.delegate?.walletConnect(service: strongSelf, didChange: sessions) + self.delegate?.walletConnect(service: self, didChange: sessions) } requestCancellable = client?.sessionRequestPublisher .receive(on: DispatchQueue.main) .sink { [weak self] request in - guard let strongSelf = self else { + guard let self = self else { return } - let session = strongSelf.client?.getSessions().first { $0.topic == request.topic } + let session = self.client?.getSessions().first { $0.topic == request.topic } - strongSelf.delegate?.walletConnect( - service: strongSelf, + self.delegate?.walletConnect( + service: self, request: request, session: session ) diff --git a/novawallet/Common/View/StackTable/StackWalletAmountCell.swift b/novawallet/Common/View/StackTable/StackWalletAmountCell.swift index 32e6771619..ef086d9988 100644 --- a/novawallet/Common/View/StackTable/StackWalletAmountCell.swift +++ b/novawallet/Common/View/StackTable/StackWalletAmountCell.swift @@ -12,7 +12,7 @@ final class StackWalletAmountCell: RowView 1 { - if row == count - 1 { - self = .bottom - } else if row == 0 { - self = .top - } else { - self = .middle - } - } else { + guard count > 1 else { self = .single + return + } + + if row == count - 1 { + self = .bottom + } else if row == 0 { + self = .top + } else { + self = .middle } } } diff --git a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift index 2c31975320..25c7067613 100644 --- a/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift +++ b/novawallet/Common/ViewController/ModalPicker/ModalNetworksFactory.swift @@ -124,12 +124,12 @@ enum ModalNetworksFactory { } static func createResolutionInfoList( - for required: DAppChainsResolution, - optional: DAppChainsResolution? + for requiredResolution: DAppChainsResolution, + optionalResolution: DAppChainsResolution? ) -> UIViewController? { let viewController: ModalPickerViewController - let networksCount = required.totalChainsCount + (optional?.totalChainsCount ?? 0) + let networksCount = requiredResolution.totalChainsCount + (optionalResolution?.totalChainsCount ?? 0) let title = LocalizableResource { locale in R.string.localizable.commonNetworksTitle( @@ -140,14 +140,14 @@ enum ModalNetworksFactory { viewController = createDAppsNetworksController(for: title) - let rowsCount = required.resolved.count + (optional?.resolved.count ?? 0) + let rowsCount = requiredResolution.resolved.count + (optionalResolution?.resolved.count ?? 0) var sectionsCount: Int = 0 var footersCount: Int = 0 - if required.hasChains { + if requiredResolution.hasChains { let hasFooter = addNetworksSection( to: viewController, - from: required, + from: requiredResolution, title: LocalizableResource { locale in R.string.localizable.dappsRequiredNetworks(preferredLanguages: locale.rLanguages) } @@ -160,10 +160,10 @@ enum ModalNetworksFactory { } } - if let optional = optional, optional.hasChains { + if let optionalResolution = optionalResolution, optionalResolution.hasChains { let hasFooter = addNetworksSection( to: viewController, - from: optional, + from: optionalResolution, title: LocalizableResource { locale in R.string.localizable.dappsOptionalNetworks(preferredLanguages: locale.rLanguages) } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift index 44c634f0ec..eabab90752 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthViewFactory.swift @@ -60,7 +60,7 @@ struct DAppWalletAuthViewFactory { } private static func createInteractor() -> DAppWalletAuthInteractor? { - guard let balancesStore = BalancesStore.createDefaut() else { + guard let balancesStore = BalancesStore.createDefault() else { return nil } diff --git a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift index 805df53f4e..d0d1724eb4 100644 --- a/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift +++ b/novawallet/Modules/DApp/DAppWalletAuth/DAppWalletAuthWireframe.swift @@ -12,7 +12,7 @@ final class DAppWalletAuthWireframe: DAppWalletAuthWireframeProtocol { ) { guard let networksView = ModalNetworksFactory.createResolutionInfoList( for: requiredResolution, - optional: optionalResolution + optionalResolution: optionalResolution ) else { return } diff --git a/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift index 3608e0aedc..150be3de47 100644 --- a/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift +++ b/novawallet/Modules/WalletsList/Choose/WalletsChooseViewFactory.swift @@ -42,7 +42,7 @@ final class WalletsChooseViewFactory { } private static func createInteractor() -> WalletsListInteractor? { - guard let balancesStore = BalancesStore.createDefaut() else { + guard let balancesStore = BalancesStore.createDefault() else { return nil } diff --git a/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift b/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift index 751574357f..b7ae197cab 100644 --- a/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift +++ b/novawallet/Modules/WalletsList/Manage/WalletManageViewFactory.swift @@ -43,7 +43,7 @@ final class WalletManageViewFactory { } private static func createInteractor() -> WalletManageInteractor? { - guard let balancesStore = BalancesStore.createDefaut() else { + guard let balancesStore = BalancesStore.createDefault() else { return nil } diff --git a/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift b/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift index 3f982a0fa8..c3e973319e 100644 --- a/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift +++ b/novawallet/Modules/WalletsList/Selection/WalletSelectionViewFactory.swift @@ -36,7 +36,7 @@ struct WalletSelectionViewFactory { } private static func createInteractor() -> WalletSelectionInteractor? { - guard let balancesStore = BalancesStore.createDefaut() else { + guard let balancesStore = BalancesStore.createDefault() else { return nil } From b813e5282b0a61cfba1bcabcb32ee9fb654ecc71 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 18:45:04 +0500 Subject: [PATCH 47/54] sync localize --- novawallet/en.lproj/Localizable.strings | 21 +- novawallet/en.lproj/Localizable.stringsdict | 358 ++++++++-------- novawallet/ru.lproj/Localizable.strings | 25 +- novawallet/ru.lproj/Localizable.stringsdict | 434 ++++++++++---------- 4 files changed, 424 insertions(+), 414 deletions(-) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index 5a84db345d..b903266ecd 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -164,7 +164,7 @@ "transaction.status.completed" = "Completed"; "transaction.status.failed" = "Failed"; "transaction.status.pending" = "Pending"; -"transaction.detail.status" = "Status"; +"common.status" = "Status"; "transaction.detail.date" = "Date"; "transaction.details.from" = "From"; "wallet.balance.locked" = "Locked"; @@ -1208,21 +1208,20 @@ "transfer.setup.recipient.input.placeholder" = "Address or w3n"; "transfer.setup.error.w3n.search.in.progress.title" = "Searching name is in progress"; "transfer.setup.error.w3n.search.in.progress.subtitle" = "Please, wait until searching %@ is finished"; -"common.wallet.connect" = "WalletConnect"; -"common.wallet.connect.v2" = "WalletConnect v2"; "wallet.connect.scan.message" = "Scan the QR code"; -"wallet.connect.scan.error" = "Invalid URI format"; +"common.wallet.connect" = "WalletConnect"; "wallet.connect.scan.button" = "New connection"; -"common.status.active" = "Active"; -"common.status.expired" = "Expired"; "common.disconnect" = "Disconnect"; -"common.status" = "Status"; "common.none" = "None"; -"dapps.missing.required.networks.warning.format" = "Some of the required networks requested by “%@” are not supported in Nova Wallet"; -"dapps.required.networks" = "Required"; -"dapps.optional.networks" = "Optional"; +"common.status.active" = "Active"; "dapps.request.sign.title" = "Sign Request"; +"common.wallet.connect.v2" = "WalletConnect v2"; +"dapps.missing.required.networks.warning.format" = "Some of the required networks requested by \"%@\" are not supported in Nova Wallet"; +"dapps.optional.networks" = "Optional"; +"dapps.required.networks" = "Required"; +"common.status.expired" = "Expired"; +"wallet.connect.scan.error" = "Invalid URI format"; "wallet.connect.pairing.error" = "Could not connect to the dapp due to the network issues"; "wallet.connect.disconnect.error" = "Could not close session with the dapp due to the network issues"; "wallet.connect.signature.submit.error" = "Could not submit signature to the dapp due to the network issues"; -"wallet.connect.proposal.result.submit.error" = "Could not authorize the dapp due to the network issues"; +"wallet.connect.proposal.result.submit.error" = "Could not authorize the dapp due to the network issues"; \ No newline at end of file diff --git a/novawallet/en.lproj/Localizable.stringsdict b/novawallet/en.lproj/Localizable.stringsdict index 651f08a61c..927e312833 100644 --- a/novawallet/en.lproj/Localizable.stringsdict +++ b/novawallet/en.lproj/Localizable.stringsdict @@ -1,182 +1,182 @@ - - common.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li day - other - %li days - - - common.hours.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li hour - other - %li hours - - - common.days.left.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li day left - other - %li days left - - - staking.analytics.validators.eras.counter - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li era - other - %li eras - - - common.minutes.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li minute - other - %li minutes - - - common.every.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - everyday - other - every %li days - - - common.in.tracks - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 track - other - %li tracks - - - common.networks.title - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - Network - other - Networks - - - common.unsupported.count - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 unsupported - other - %li unsupported - - - missing.accounts.warning.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %2$@ account is missing. Add account to the wallet in Settings - other - %2$@ accounts are missing. Add accounts to the wallet in Settings - - - dapps.unsupported.networks.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 unsupported network hidden - other - %li unsupported networks hidden - - - - + + common.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li day + other + %li days + + + common.hours.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li hour + other + %li hours + + + common.days.left.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li day left + other + %li days left + + + staking.analytics.validators.eras.counter + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li era + other + %li eras + + + common.minutes.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li minute + other + %li minutes + + + common.every.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + everyday + other + every %li days + + + common.in.tracks + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 track + other + %li tracks + + + common.networks.title + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Network + other + Networks + + + common.unsupported.count + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 unsupported + other + %li unsupported + + + missing.accounts.warning.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %2$@ account is missing. Add account to the wallet in Settings + other + %2$@ accounts are missing. Add accounts to the wallet in Settings + + + dapps.unsupported.networks.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 unsupported network hidden + other + %li unsupported networks hidden + + + + \ No newline at end of file diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 0008c6371a..c10b10a39c 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -164,7 +164,7 @@ "transaction.status.completed" = "Успешно"; "transaction.status.failed" = "Ошибка"; "transaction.status.pending" = "В ожидании"; -"transaction.detail.status" = "Статус"; +"common.status" = "Статус"; "transaction.detail.date" = "Дата"; "transaction.details.from" = "От"; "wallet.balance.locked" = "Заблокировано"; @@ -1208,21 +1208,20 @@ "transfer.setup.recipient.input.placeholder" = "Адрес или w3n"; "transfer.setup.error.w3n.search.in.progress.title" = "Идет поиск адреса получателя"; "transfer.setup.error.w3n.search.in.progress.subtitle" = "Пожалуйста, подождите пока завершится поиск имени %@"; +"wallet.connect.scan.message" = "Отсканируйте QR-код"; "common.wallet.connect" = "WalletConnect"; -"common.wallet.connect.v2" = "WalletConnect v2"; -"wallet.connect.scan.message" = "Отсканируйте QR код"; -"wallet.connect.scan.error" = "Неверный формат URI"; -"wallet.connect.scan.button" = "Новое соединение"; -"common.status.active" = "Активный"; -"common.status.expired" = "Истёк"; +"wallet.connect.scan.button" = "Новое подключение"; "common.disconnect" = "Отключить"; -"common.status" = "Статус"; -"common.none" = "Отсутствуют"; -"dapps.missing.required.networks.warning.format" = "Некоторые запрошенные “%@” сети не поддерживаются в Nova Wallet"; +"common.none" = "Нет"; +"common.status.active" = "Активно"; +"dapps.request.sign.title" = "Запрос на подпись"; +"common.wallet.connect.v2" = "WalletConnect v2"; +"dapps.missing.required.networks.warning.format" = "Некоторые из обязательных сетей, запрошенных \"%@\", не поддерживаются в Nova Wallet"; +"dapps.optional.networks" = "Необязательные"; "dapps.required.networks" = "Обязательные"; -"dapps.optional.networks" = "Не обязательные"; -"dapps.request.sign.title" = "Подпишите запрос"; +"common.status.expired" = "Истекла"; +"wallet.connect.scan.error" = "Неверный формат URI"; "wallet.connect.pairing.error" = "Не удалось подключиться к dapp из-за проблем с сетью"; "wallet.connect.disconnect.error" = "Не удалось завершить сессию с dapp из-за проблем с сетью"; "wallet.connect.signature.submit.error" = "Не удалось отправить подпись dapp из-за проблем с сетью"; -"wallet.connect.proposal.result.submit.error" = "Не удалось авторизовать dapp из-за проблем с сетью"; +"wallet.connect.proposal.result.submit.error" = "Не удалось авторизовать dapp из-за проблем с сетью"; \ No newline at end of file diff --git a/novawallet/ru.lproj/Localizable.stringsdict b/novawallet/ru.lproj/Localizable.stringsdict index 89495f504e..77177a340a 100644 --- a/novawallet/ru.lproj/Localizable.stringsdict +++ b/novawallet/ru.lproj/Localizable.stringsdict @@ -1,214 +1,226 @@ - - common.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li день - few - %li дня - many - %li дней - other - %li дней - - - common.hours.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li час - few - %li часа - many - %li часов - other - %li час - - - common.days.left.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - Остался %li день - few - Осталось %li дня - many - Осталось %li дней - other - Осталось %li дней - - - staking.analytics.validators.eras.counter - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li эра - few - %li эр - many - %li эр - other - %li эры - - - common.minutes.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %li минута - few - %li минуты - many - %li минут - other - %li минута - - - common.every.days.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - каждый день - few - каждые %li дня - many - каждые %li дней - other - каждые %li дней - - - common.in.tracks - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 треке - few - %li треках - many - %li треках - other - %li треках - - - common.networks.title - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - Сеть - other - Сети - - - common.unsupported.count - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 не поддерживается - other - %li не поддерживаются - - - missing.accounts.warning.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - %2$@ аккаунт не найден. Добавьте аккаунт в кошелек в Настройках - other - %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках - - - dapps.unsupported.networks.format - - NSStringLocalizedFormatKey - %#@format@ - format - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - li - one - 1 не поддерживаемая сеть скрыта - few - %li не поддерживаемые сети скрыты - many - %li не поддерживаемых сетей скрыто - other - %li не поддерживаемых сетей скрыто - - - - + + common.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li день + few + %li дня + many + %li дней + other + %li дней + + + common.hours.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li час + few + %li часа + many + %li часов + other + %li час + + + common.days.left.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Остался %li день + few + Осталось %li дня + many + Осталось %li дней + other + Осталось %li дней + + + staking.analytics.validators.eras.counter + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li эра + few + %li эр + many + %li эр + other + %li эры + + + common.minutes.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %li минута + few + %li минуты + many + %li минут + other + %li минута + + + common.every.days.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + каждый день + few + каждые %li дня + many + каждые %li дней + other + каждые %li дней + + + common.in.tracks + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 треке + few + %li треках + many + %li треках + other + %li треках + + + common.networks.title + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + Сеть + few + Сети + many + Сети + other + Сети + + + common.unsupported.count + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 не поддерживается + few + %li не поддерживаются + many + %li не поддерживаются + other + %li не поддерживаются + + + missing.accounts.warning.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + %2$@ аккаунт не найден. Добавьте аккаунт в кошелек в Настройках + few + %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках + many + %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках + other + %2$@ аккаунты не найдены. Добавьте аккаунты в кошелек в Настройках + + + dapps.unsupported.networks.format + + NSStringLocalizedFormatKey + %#@format@ + format + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + li + one + 1 не поддерживаемая сеть скрыта + few + %li не поддерживаемые сети скрыты + many + %li не поддерживаемых сетей скрыто + other + %li не поддерживаемых сетей скрыто + + + + \ No newline at end of file From 05d0a994b65c9628dbb6875781019aa217dca7aa Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 18:47:14 +0500 Subject: [PATCH 48/54] fix unsupported message text --- .../WalletConnect/States/WalletConnectStateNewMessage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift index ffe2962c47..d4e48f49c1 100644 --- a/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift +++ b/novawallet/Modules/DApp/WalletConnect/States/WalletConnectStateNewMessage.swift @@ -128,7 +128,7 @@ final class WalletConnectStateNewMessage: WalletConnectBaseState { } guard let method = WalletConnectMethod(rawValue: request.method) else { - rejectRequest(request: request, reason: "unsupported method: \(request.method)") + rejectRequest(request: request, reason: "unsupported method \(request.method)") return } From 3eaf025d553e7ad96ad5d25e083392c37b026a3a Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 11 May 2023 19:00:53 +0500 Subject: [PATCH 49/54] add wc project id to github --- .github/workflows/deploy_staging.yml | 1 + .github/workflows/pull_request.yml | 1 + .github/workflows/push_develop.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/deploy_staging.yml b/.github/workflows/deploy_staging.yml index 08dff90650..a17c084f15 100644 --- a/.github/workflows/deploy_staging.yml +++ b/.github/workflows/deploy_staging.yml @@ -102,6 +102,7 @@ jobs: ACALA_TEST_AUTH_TOKEN: ${{ secrets.ACALA_TEST_AUTH_TOKEN }} MOONBEAM_API_KEY: ${{ secrets.MOONBEAM_API_KEY }} MOONBEAM_TEST_API_KEY: ${{ secrets.MOONBEAM_TEST_API_KEY }} + WC_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} run: xcodebuild archive -archivePath ./AdHoc.xcarchive -scheme novawallet -workspace novawallet.xcworkspace -configuration ${{ github.event.inputs.config }} - name: Export Staging archive diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7c6c0c0742..69d31dc8bb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -55,6 +55,7 @@ jobs: ACALA_TEST_AUTH_TOKEN: ${{ secrets.ACALA_TEST_AUTH_TOKEN }} MOONBEAM_API_KEY: ${{ secrets.MOONBEAM_API_KEY }} MOONBEAM_TEST_API_KEY: ${{ secrets.MOONBEAM_TEST_API_KEY }} + WC_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} run: | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) # device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}'` diff --git a/.github/workflows/push_develop.yml b/.github/workflows/push_develop.yml index a20f2d258f..775cf3dc88 100644 --- a/.github/workflows/push_develop.yml +++ b/.github/workflows/push_develop.yml @@ -78,6 +78,7 @@ jobs: ACALA_TEST_AUTH_TOKEN: ${{ secrets.ACALA_TEST_AUTH_TOKEN }} MOONBEAM_API_KEY: ${{ secrets.MOONBEAM_API_KEY }} MOONBEAM_TEST_API_KEY: ${{ secrets.MOONBEAM_TEST_API_KEY }} + WC_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} run: xcodebuild archive -archivePath ./AdHoc.xcarchive -scheme novawallet -workspace novawallet.xcworkspace -configuration Dev - name: Export archive From 63fc7db145e1bd6ea1f7339ee18223ee8e1c90b6 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 12 May 2023 11:58:30 +0500 Subject: [PATCH 50/54] support 9042 runtime --- .../Common/Model/AssetStorageInfo.swift | 23 +++++++++++++------ .../RuntimeCoderFactory.swift | 6 +++++ .../Calls/Common/SubstrateCallFactory.swift | 8 ++++--- .../Common/Substrate/Types/AccountInfo.swift | 16 +++++++++---- .../Substrate/Types/CallCodingPath.swift | 6 ++++- .../OnChain/OnChainTransferInteractor.swift | 23 ++++++++++++------- .../Acala/AcalaBonusService.swift | 2 +- .../Modules/Wallet/Model/BalanceContext.swift | 22 ------------------ 8 files changed, 60 insertions(+), 46 deletions(-) diff --git a/novawallet/Common/Model/AssetStorageInfo.swift b/novawallet/Common/Model/AssetStorageInfo.swift index ef31d8269f..5ef08651bc 100644 --- a/novawallet/Common/Model/AssetStorageInfo.swift +++ b/novawallet/Common/Model/AssetStorageInfo.swift @@ -14,8 +14,13 @@ struct OrmlTokenStorageInfo { let canTransferAll: Bool } +struct NativeTokenStorageInfo { + let canTransferAll: Bool + let transferCallPath: CallCodingPath +} + enum AssetStorageInfo { - case native(canTransferAll: Bool) + case native(info: NativeTokenStorageInfo) case statemine(extras: StatemineAssetExtras) case orml(info: OrmlTokenStorageInfo) case erc20(contractAccount: AccountId) @@ -62,12 +67,16 @@ extension AssetStorageInfo { return .equilibrium(extras: extras) case .none: - let call = CallCodingPath.transferAll - let canTransferAll = codingFactory.metadata.getCall( - from: call.moduleName, - with: call.callName - ) != nil - return .native(canTransferAll: canTransferAll) + let canTransferAll = codingFactory.hasCall(for: .transferAll) + let transferCallPath: CallCodingPath = codingFactory.hasCall(for: .transferAllowDeath) ? + .transferAllowDeath : .transfer + + let info = NativeTokenStorageInfo( + canTransferAll: canTransferAll, + transferCallPath: transferCallPath + ) + + return .native(info: info) } } diff --git a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeCoderFactory.swift b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeCoderFactory.swift index a67e476e72..f0945e549c 100644 --- a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeCoderFactory.swift +++ b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeCoderFactory.swift @@ -15,6 +15,12 @@ protocol RuntimeCoderFactoryProtocol { func getCall(for codingPath: CallCodingPath) -> CallMetadata? } +extension RuntimeCoderFactoryProtocol { + func hasCall(for codingPath: CallCodingPath) -> Bool { + getCall(for: codingPath) != nil + } +} + final class RuntimeCoderFactory: RuntimeCoderFactoryProtocol { static let addressTypeName = "Address" diff --git a/novawallet/Common/Substrate/Calls/Common/SubstrateCallFactory.swift b/novawallet/Common/Substrate/Calls/Common/SubstrateCallFactory.swift index fd12bca31b..849f72a36c 100644 --- a/novawallet/Common/Substrate/Calls/Common/SubstrateCallFactory.swift +++ b/novawallet/Common/Substrate/Calls/Common/SubstrateCallFactory.swift @@ -6,7 +6,8 @@ import BigInt protocol SubstrateCallFactoryProtocol { func nativeTransfer( to receiver: AccountId, - amount: BigUInt + amount: BigUInt, + callPath: CallCodingPath ) -> RuntimeCall func nativeTransferAll(to receiver: AccountId) -> RuntimeCall @@ -169,10 +170,11 @@ final class SubstrateCallFactory: SubstrateCallFactoryProtocol { func nativeTransfer( to receiver: AccountId, - amount: BigUInt + amount: BigUInt, + callPath: CallCodingPath ) -> RuntimeCall { let args = TransferCall(dest: .accoundId(receiver), value: amount) - return RuntimeCall(moduleName: "Balances", callName: "transfer", args: args) + return RuntimeCall(moduleName: callPath.moduleName, callName: callPath.callName, args: args) } func nativeTransferAll(to receiver: AccountId) -> RuntimeCall { diff --git a/novawallet/Common/Substrate/Types/AccountInfo.swift b/novawallet/Common/Substrate/Types/AccountInfo.swift index b8df2f41cd..28f80eec87 100644 --- a/novawallet/Common/Substrate/Types/AccountInfo.swift +++ b/novawallet/Common/Substrate/Types/AccountInfo.swift @@ -10,13 +10,21 @@ struct AccountInfo: Codable, Equatable { struct AccountData: Codable, Equatable { @StringCodable var free: BigUInt @StringCodable var reserved: BigUInt - @StringCodable var miscFrozen: BigUInt - @StringCodable var feeFrozen: BigUInt + @OptionStringCodable var frozen: BigUInt? + @OptionStringCodable var miscFrozen: BigUInt? + @OptionStringCodable var feeFrozen: BigUInt? } extension AccountData { var total: BigUInt { free + reserved } - var frozen: BigUInt { reserved + locked } - var locked: BigUInt { max(miscFrozen, feeFrozen) } + + var locked: BigUInt { + if let feeFrozen = feeFrozen, let miscFrozen = miscFrozen { + return max(miscFrozen, feeFrozen) + } else { + return frozen ?? 0 + } + } + var available: BigUInt { free > locked ? free - locked : 0 } } diff --git a/novawallet/Common/Substrate/Types/CallCodingPath.swift b/novawallet/Common/Substrate/Types/CallCodingPath.swift index 7e3196b51a..207ee2aef6 100644 --- a/novawallet/Common/Substrate/Types/CallCodingPath.swift +++ b/novawallet/Common/Substrate/Types/CallCodingPath.swift @@ -15,7 +15,7 @@ extension CallCodingPath { } var isBalancesTransfer: Bool { - [.transfer, .transferKeepAlive, .forceTransfer, .transferAll].contains(self) + [.transfer, .transferAllowDeath, .transferKeepAlive, .forceTransfer, .transferAll].contains(self) } var isAssetsTransfer: Bool { @@ -52,6 +52,10 @@ extension CallCodingPath { CallCodingPath(moduleName: "Balances", callName: "transfer_keep_alive") } + static var transferAllowDeath: CallCodingPath { + CallCodingPath(moduleName: "Balances", callName: "transfer_allow_death") + } + static var forceTransfer: CallCodingPath { CallCodingPath(moduleName: "Balances", callName: "force_transfer") } diff --git a/novawallet/Modules/Transfer/BaseTransfer/OnChain/OnChainTransferInteractor.swift b/novawallet/Modules/Transfer/BaseTransfer/OnChain/OnChainTransferInteractor.swift index a76ee576ad..4295dfdff1 100644 --- a/novawallet/Modules/Transfer/BaseTransfer/OnChain/OnChainTransferInteractor.swift +++ b/novawallet/Modules/Transfer/BaseTransfer/OnChain/OnChainTransferInteractor.swift @@ -234,20 +234,26 @@ class OnChainTransferInteractor: OnChainTransferBaseInteractor, RuntimeConstantF to builder: ExtrinsicBuilderProtocol, amount: OnChainTransferAmount, recepient: AccountId, - canTransferAll: Bool + info: NativeTokenStorageInfo ) throws -> (ExtrinsicBuilderProtocol, CallCodingPath?) { switch amount { case let .concrete(value): return try addingNativeTransferValueCommand( to: builder, recepient: recepient, - value: value + value: value, + callPath: info.transferCallPath ) case let .all(value): - if canTransferAll { + if info.canTransferAll { return try addingNativeTransferAllCommand(to: builder, recepient: recepient) } else { - return try addingNativeTransferValueCommand(to: builder, recepient: recepient, value: value) + return try addingNativeTransferValueCommand( + to: builder, + recepient: recepient, + value: value, + callPath: info.transferCallPath + ) } } } @@ -255,9 +261,10 @@ class OnChainTransferInteractor: OnChainTransferBaseInteractor, RuntimeConstantF func addingNativeTransferValueCommand( to builder: ExtrinsicBuilderProtocol, recepient: AccountId, - value: BigUInt + value: BigUInt, + callPath: CallCodingPath ) throws -> (ExtrinsicBuilderProtocol, CallCodingPath?) { - let call = callFactory.nativeTransfer(to: recepient, amount: value) + let call = callFactory.nativeTransfer(to: recepient, amount: value, callPath: callPath) let newBuilder = try builder.adding(call: call) return (newBuilder, CallCodingPath(moduleName: call.moduleName, callName: call.callName)) } @@ -327,12 +334,12 @@ class OnChainTransferInteractor: OnChainTransferBaseInteractor, RuntimeConstantF recepient: recepient, extras: extras ) - case let .native(canTransferAll): + case let .native(info): return try addingNativeTransferCommand( to: builder, amount: amount, recepient: recepient, - canTransferAll: canTransferAll + info: info ) case let .equilibrium(extras): return try addingEquilibriumTransferCommand( diff --git a/novawallet/Modules/Vote/Crowdloan/CustomCrowdloan/Acala/AcalaBonusService.swift b/novawallet/Modules/Vote/Crowdloan/CustomCrowdloan/Acala/AcalaBonusService.swift index 2619f64681..13dd0dd5bf 100644 --- a/novawallet/Modules/Vote/Crowdloan/CustomCrowdloan/Acala/AcalaBonusService.swift +++ b/novawallet/Modules/Vote/Crowdloan/CustomCrowdloan/Acala/AcalaBonusService.swift @@ -287,7 +287,7 @@ extension AcalaBonusService: CrowdloanBonusServiceProtocol { } let builder = builder.reset() let callFactory = SubstrateCallFactory() - let transferCall = callFactory.nativeTransfer(to: accountId, amount: amount) + let transferCall = callFactory.nativeTransfer(to: accountId, amount: amount, callPath: .transfer) let statementRemark = callFactory.remarkWithEvent(remark: statement) if diff --git a/novawallet/Modules/Wallet/Model/BalanceContext.swift b/novawallet/Modules/Wallet/Model/BalanceContext.swift index a7f4ff6494..3e9aabc1ef 100644 --- a/novawallet/Modules/Wallet/Model/BalanceContext.swift +++ b/novawallet/Modules/Wallet/Model/BalanceContext.swift @@ -91,28 +91,6 @@ extension BalanceContext { } extension BalanceContext { - func byChangingAccountInfo(_ accountData: AccountData, precision: Int16) -> BalanceContext { - let free = Decimal - .fromSubstrateAmount(accountData.free, precision: precision) ?? .zero - let reserved = Decimal - .fromSubstrateAmount(accountData.reserved, precision: precision) ?? .zero - let miscFrozen = Decimal - .fromSubstrateAmount(accountData.miscFrozen, precision: precision) ?? .zero - let feeFrozen = Decimal - .fromSubstrateAmount(accountData.feeFrozen, precision: precision) ?? .zero - - return BalanceContext( - free: free, - reserved: reserved, - frozen: max(miscFrozen, feeFrozen), - crowdloans: crowdloans, - price: price, - priceChange: priceChange, - priceId: priceId, - balanceLocks: balanceLocks - ) - } - func byChangingAssetBalance(_ assetBalance: AssetBalance, precision: Int16) -> BalanceContext { let free = Decimal .fromSubstrateAmount(assetBalance.freeInPlank, precision: precision) ?? .zero From 0487dfcc84e547f43b9674feba52ccc1a4ae7bae Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 12 May 2023 13:53:59 +0500 Subject: [PATCH 51/54] fix staking wiki links --- novawallet/Common/Configs/ApplicationConfigs.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index 71489ec22a..be1089d96d 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -184,12 +184,13 @@ extension ApplicationConfig: ApplicationConfigProtocol { } var learnPayoutURL: URL { - URL(string: "https://wiki.polkadot.network/docs/en/learn-simple-payouts")! + // swiftlint:disable:next line_length + URL(string: "https://docs.novawallet.io/nova-wallet-wiki/staking/staking-faq#q-what-is-the-difference-between-restake-rewards-and-transferable-rewards")! } var learnControllerAccountURL: URL { // swiftlint:disable:next line_length - URL(string: "https://wiki.polkadot.network/docs/en/maintain-guides-how-to-nominate-polkadot#setting-up-stash-and-controller-keys")! + URL(string: "https://docs.novawallet.io/nova-wallet-wiki/staking/staking-faq#q-what-are-stash-and-controller-accounts")! } var paritySignerTroubleshoutingURL: URL { @@ -201,7 +202,8 @@ extension ApplicationConfig: ApplicationConfigProtocol { } var learnRecommendedValidatorsURL: URL { - URL(string: "https://github.com/nova-wallet/nova-utils/wiki/Recommended-validators-in-Nova-Wallet")! + // swiftlint:disable:next line_length + URL(string: "https://docs.novawallet.io/nova-wallet-wiki/staking/staking-faq#q-how-does-nova-wallet-select-validators-collators")! } var learnGovernanceDelegateMetadata: URL { From 3ef0dea5bab7ad4cc822635e14a9bb8c63ae9d62 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 12 May 2023 18:14:19 +0500 Subject: [PATCH 52/54] fix dapp tx signing --- .../DAppOperationConfirmInteractor.swift | 18 +++----- ...DAppExtrinsicBuilderOperationFactory.swift | 43 +++++++++++++------ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift index 83cbfe7ca5..2977f0e931 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmInteractor.swift @@ -99,24 +99,16 @@ final class DAppOperationConfirmInteractor: DAppOperationBaseInteractor { for extrinsicFactory: DAppExtrinsicBuilderOperationFactory, signer: SigningWrapperProtocol ) -> CompoundOperationWrapper { - let signatureWrapper = extrinsicFactory.createWrapper( - customClosure: { builder, _ in builder }, - indexes: [0], - signingClosure: { data in - try signer.sign(data).rawData() - } - ) + let signatureWrapper = extrinsicFactory.createRawSignatureWrapper { data in + try signer.sign(data).rawData() + } - let codingFactoryOperation = extrinsicFactory.runtimeProvider.fetchCoderFactoryOperation() + let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() let signatureOperation = ClosureOperation { - let rawSignatures = try signatureWrapper.targetOperation.extractNoCancellableResultData() + let rawSignature = try signatureWrapper.targetOperation.extractNoCancellableResultData() let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() - guard let rawSignature = rawSignatures.first else { - throw CommonError.dataCorruption - } - let scaleEncoder = codingFactory.createEncoder() switch extrinsicFactory.processedResult.account.cryptoType { diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift index dfb1048c67..61136509b1 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/Model/DAppExtrinsicBuilderOperationFactory.swift @@ -86,22 +86,11 @@ extension DAppExtrinsicBuilderOperationFactory: ExtrinsicBuilderOperationFactory indexes _: [Int], signingClosure: @escaping (Data) throws -> Data ) -> CompoundOperationWrapper<[Data]> { - let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() - - let builderOperation = createBaseBuilderOperation( - for: processedResult, - codingFactoryOperation: codingFactoryOperation - ) - - builderOperation.addDependency(codingFactoryOperation) - let signatureWrapper = createRawSignatureOperation( for: processedResult, signingClosure: signingClosure ) - signatureWrapper.addDependency(operations: [builderOperation]) - let mappingOperation = ClosureOperation<[Data]> { let data = try signatureWrapper.targetOperation.extractNoCancellableResultData() @@ -110,7 +99,7 @@ extension DAppExtrinsicBuilderOperationFactory: ExtrinsicBuilderOperationFactory mappingOperation.addDependency(signatureWrapper.targetOperation) - let dependencies = [codingFactoryOperation, builderOperation] + signatureWrapper.allOperations + let dependencies = signatureWrapper.allOperations let wrapper = CompoundOperationWrapper(targetOperation: mappingOperation, dependencies: dependencies) @@ -120,4 +109,34 @@ extension DAppExtrinsicBuilderOperationFactory: ExtrinsicBuilderOperationFactory func createDummySigner() throws -> DummySigner { try DummySigner(cryptoType: processedResult.account.cryptoType) } + + func createRawSignatureWrapper( + for signingClosure: @escaping (Data) throws -> Data + ) -> CompoundOperationWrapper { + let codingFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + + let builderOperation = createBaseBuilderOperation( + for: processedResult, + codingFactoryOperation: codingFactoryOperation + ) + + builderOperation.addDependency(codingFactoryOperation) + + let signOperation = ClosureOperation { + let builder = try builderOperation.extractNoCancellableResultData() + let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() + + return try builder.buildRawSignature( + using: signingClosure, + encoder: codingFactory.createEncoder(), + metadata: codingFactory.metadata + ) + } + + signOperation.addDependency(builderOperation) + + let dependencies = [codingFactoryOperation, builderOperation] + + return CompoundOperationWrapper(targetOperation: signOperation, dependencies: dependencies) + } } From 8e96a94da7be252da313993b0939bc10a609043d Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 12 May 2023 18:19:47 +0500 Subject: [PATCH 53/54] fix account naming --- .../DAppOperationConfirmViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift index 4c3c2c9e03..d1f4d3187a 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift @@ -52,7 +52,7 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { rootView.walletCell.titleLabel.text = R.string.localizable.commonWallet( preferredLanguages: languages ) - rootView.accountCell.titleLabel.text = R.string.localizable.commonAccountAddress( + rootView.accountCell.titleLabel.text = R.string.localizable.commonAccount( preferredLanguages: languages ) rootView.networkCell.titleLabel.text = R.string.localizable.commonNetwork( From e9ab2e0f0f44338502012b80d9b8c11f07620635 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 13 May 2023 13:55:19 +0500 Subject: [PATCH 54/54] fix pr comments --- .../DAppInteraction/DAppInteractionError.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift index 3cd7f3e2bd..c952d88ee7 100644 --- a/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift +++ b/novawallet/Modules/DApp/DAppInteraction/DAppInteractionError.swift @@ -6,18 +6,15 @@ enum DAppInteractionError: Error { extension DAppInteractionError: ErrorContentConvertible { func toErrorContent(for locale: Locale?) -> ErrorContent { - let title: String - let message: String - switch self { case let .unexpected(reason, _): - title = R.string.localizable.commonErrorGeneralTitle(preferredLanguages: locale?.rLanguages) - message = R.string.localizable.dappUnexpectedErrorFormat( - reason, - preferredLanguages: locale?.rLanguages + return .init( + title: R.string.localizable.commonErrorGeneralTitle(preferredLanguages: locale?.rLanguages), + message: R.string.localizable.dappUnexpectedErrorFormat( + reason, + preferredLanguages: locale?.rLanguages + ) ) } - - return ErrorContent(title: title, message: message) } }