diff --git a/MobileWallet.xcodeproj/project.pbxproj b/MobileWallet.xcodeproj/project.pbxproj index 8281e5cd..0442a08f 100644 --- a/MobileWallet.xcodeproj/project.pbxproj +++ b/MobileWallet.xcodeproj/project.pbxproj @@ -11,24 +11,16 @@ 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CDB238011CA00FA7002 /* PublicKey.swift */; }; 001F6CE02380198D00FA7002 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CDF2380198D00FA7002 /* Contacts.swift */; }; 001F6CE42380253B00FA7002 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CE32380253B00FA7002 /* Contact.swift */; }; - 001F6CE82381A22E00FA7002 /* CompletedTxs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CE72381A22E00FA7002 /* CompletedTxs.swift */; }; - 001F6CEC2381A39300FA7002 /* CompletedTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F6CEB2381A39300FA7002 /* CompletedTx.swift */; }; 00230AFA2376C6DE00F23E34 /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00230AF92376C6DD00F23E34 /* Sequence.swift */; }; 00230B0923770CEA00F23E34 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00230B0823770CEA00F23E34 /* CALayer.swift */; }; 00262EB4236F024400A6C8A0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00262EB3236F024400A6C8A0 /* Theme.swift */; }; 00325ECA239E4A1000F76918 /* FadedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00325EC9239E4A1000F76918 /* FadedOverlayView.swift */; }; - 00369D812426080D00BAB95B /* TariLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00369D802426080D00BAB95B /* TariLogger.swift */; }; 0039B1B223FA925500F91E0F /* PasteEmojisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039B1B123FA925500F91E0F /* PasteEmojisView.swift */; }; 004277F323E0407900AE7BD9 /* TextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277F223E0407900AE7BD9 /* TextButton.swift */; }; 004277F723E0628A00AE7BD9 /* UIViewController+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */; }; - 004277FB23E1A61F00AE7BD9 /* ErrorDescriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277FA23E1A61F00AE7BD9 /* ErrorDescriptions.swift */; }; 004277FF23E2F1D700AE7BD9 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004277FE23E2F1D700AE7BD9 /* UIImage.swift */; }; 0042780323E8421200AE7BD9 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0042780223E8421100AE7BD9 /* String.swift */; }; 0048AF43245ADCA300E1A359 /* env.json in Resources */ = {isa = PBXBuildFile; fileRef = 0048AF42245ADCA300E1A359 /* env.json */; }; - 004997AB2382E893000A0B7D /* PendingOutboundTxs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004997AA2382E893000A0B7D /* PendingOutboundTxs.swift */; }; - 004997AF2382E938000A0B7D /* PendingOutboundTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004997AE2382E938000A0B7D /* PendingOutboundTx.swift */; }; - 004997B32382ED68000A0B7D /* PendingInboundTx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004997B22382ED68000A0B7D /* PendingInboundTx.swift */; }; - 004997B72382ED79000A0B7D /* PendingInboundTxs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004997B62382ED79000A0B7D /* PendingInboundTxs.swift */; }; 005387242405136700901A68 /* AddNoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005387232405136700901A68 /* AddNoteViewController.swift */; }; 0053873024065F6C00901A68 /* SlideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0053872F24065F6C00901A68 /* SlideView.swift */; }; 00665E7524E422C500030A13 /* CustomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00665E7424E422C500030A13 /* CustomTabBar.swift */; }; @@ -39,18 +31,11 @@ 0087A1A923F4235400B89EE7 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A523F4235400B89EE7 /* ScanViewController.swift */; }; 0087A1AF23F4235400B89EE7 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A723F4235400B89EE7 /* ContactCell.swift */; }; 0087A1B223F4235400B89EE7 /* AddRecipientViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0087A1A823F4235400B89EE7 /* AddRecipientViewController.swift */; }; - 0091D4A723ED879E004BF7F7 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0091D4A623ED879E004BF7F7 /* Signature.swift */; }; 0091D4AB23ED87BE004BF7F7 /* KeyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0091D4AA23ED87BE004BF7F7 /* KeyServer.swift */; }; - 00947CC223CF090D00526DD5 /* TxProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00947CC123CF090D00526DD5 /* TxProtocol.swift */; }; - 00947CC723D1A35200526DD5 /* TxsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00947CC623D1A35200526DD5 /* TxsProtocol.swift */; }; - 009BF8AC237ABB4700C02E44 /* TariLib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009BF8AB237ABB4700C02E44 /* TariLib.swift */; }; 009DFCA72473E33F0061DBC9 /* DeepLinkParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009DFCA62473E33F0061DBC9 /* DeepLinkParams.swift */; }; - 009DFCB124741F9B0061DBC9 /* WalletTxs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009DFCB024741F9B0061DBC9 /* WalletTxs.swift */; }; - 00A057AF23D875840029C22A /* TariEventBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A057AE23D875840029C22A /* TariEventBus.swift */; }; 00A6651F2437AD9A0046E730 /* SplashAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = 00A6651E2437AD9A0046E730 /* SplashAnimation.json */; }; 00A66523243B3E380046E730 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A66522243B3E380046E730 /* ErrorView.swift */; }; 00A66527243C766D0046E730 /* ConnectionMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A66526243C766D0046E730 /* ConnectionMonitor.swift */; }; - 00A9190B242CDD7700B7F14D /* Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A9190A242CDD7700B7F14D /* Tracker.swift */; }; 00A994332396434B007D9990 /* PulseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A994322396434B007D9990 /* PulseButton.swift */; }; 00B084C0249E471800F7B9BD /* EmojiSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B084BF249E471800F7B9BD /* EmojiSet.swift */; }; 00B084C5249E4F6900F7B9BD /* SeedWords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B084C4249E4F6900F7B9BD /* SeedWords.swift */; }; @@ -61,12 +46,8 @@ 00BBD809237D80C800EBF5E6 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BBD808237D80C800EBF5E6 /* Date.swift */; }; 00BBD80E237DA07400EBF5E6 /* ByteVector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */; }; 00BBD812237E9EE200EBF5E6 /* PrivateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BBD811237E9EE200EBF5E6 /* PrivateKey.swift */; }; - 00BBD816237EA67600EBF5E6 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BBD815237EA67600EBF5E6 /* Wallet.swift */; }; 00BBD81A237EB31900EBF5E6 /* CommsConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BBD819237EB31900EBF5E6 /* CommsConfig.swift */; }; - 00BCE47F240D5A9700B181F3 /* OnionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BCE47D240D5A9700B181F3 /* OnionManager.swift */; }; - 00BCE482240D5A9700B181F3 /* OnionConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BCE47E240D5A9700B181F3 /* OnionConnector.swift */; }; 00BCE487240D5C4100B181F3 /* Tor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00BCE486240D5C4100B181F3 /* Tor.framework */; }; - 00BCE489240EB1EE00B181F3 /* SplashViewControllerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BCE488240EB1EE00B181F3 /* SplashViewControllerSetup.swift */; }; 00C185B9238943980096984E /* UIViewControllerDebugExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C185B8238943980096984E /* UIViewControllerDebugExtension.swift */; }; 00C185BE238953F60096984E /* DebugLogsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C185BD238953F60096984E /* DebugLogsTableViewController.swift */; }; 00D7BD8924349AE3009DD097 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00D7BD8824349AE3009DD097 /* UIDevice.swift */; }; @@ -143,6 +124,8 @@ 3A0124A527B3EDEB00A481F4 /* TransactionViewControllable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0124A427B3EDEB00A481F4 /* TransactionViewControllable.swift */; }; 3A03682E27DF20E300F304A2 /* TransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A03682D27DF20E300F304A2 /* TransactionDetailsViewController.swift */; }; 3A03683127DF211D00F304A2 /* TransactionDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A03683027DF211D00F304A2 /* TransactionDetailsView.swift */; }; + 3A03A600288932DE00788A02 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A03A5FF288932DE00788A02 /* SplashView.swift */; }; + 3A03A602288962FA00788A02 /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A03A601288962FA00788A02 /* SplashViewModel.swift */; }; 3A052BF427B2658500C93671 /* AppVersionFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A052BF327B2658500C93671 /* AppVersionFormatter.swift */; }; 3A052BFA27B2940900C93671 /* WalletTransactionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A052BF927B2940900C93671 /* WalletTransactionsManager.swift */; }; 3A0EA97A26AA8E1C002612D4 /* TokenCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0EA97926AA8E1C002612D4 /* TokenCollectionView.swift */; }; @@ -160,6 +143,10 @@ 3A2B956A28648D860085BE21 /* CChar+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2B956928648D860085BE21 /* CChar+Helpers.swift */; }; 3A2C0B8727DA22970018C5A8 /* SeedWordListElementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2C0B8627DA22970018C5A8 /* SeedWordListElementView.swift */; }; 3A2C0B8927DA22C20018C5A8 /* ExpandButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2C0B8827DA22C20018C5A8 /* ExpandButton.swift */; }; + 3A2F30B428F5940A0095E25D /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F30B328F5940A0095E25D /* ConsoleLogger.swift */; }; + 3A2F30B628F594520095E25D /* FileLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F30B528F594520095E25D /* FileLogger.swift */; }; + 3A2F30B828F5946B0095E25D /* CrashLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F30B728F5946B0095E25D /* CrashLogger.swift */; }; + 3A312FEE28B6316D00A290D3 /* CompletedTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A312FED28B6316D00A290D3 /* CompletedTransactions.swift */; }; 3A35412C26A72D9F002AB5A8 /* ViewIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35412B26A72D9F002AB5A8 /* ViewIdentifiable.swift */; }; 3A35413126A72DEE002AB5A8 /* SelectBaseNodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413026A72DEE002AB5A8 /* SelectBaseNodeCell.swift */; }; 3A35413626A738D4002AB5A8 /* RoundedInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */; }; @@ -177,6 +164,7 @@ 3A4376E5269C0C10006107B0 /* RestoreWalletFromSeedsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4376E4269C0C10006107B0 /* RestoreWalletFromSeedsViewController.swift */; }; 3A4376E9269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4376E8269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift */; }; 3A4376EE269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4376ED269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift */; }; + 3A47ABCD27478208007F351B /* Tari.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A47ABCC27478208007F351B /* Tari.swift */; }; 3A4949A5283FD603002768AE /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4949A4283FD603002768AE /* AboutViewController.swift */; }; 3A494A422716D6DA00CF5B05 /* AddRecipientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A494A412716D6DA00CF5B05 /* AddRecipientView.swift */; }; 3A4BF7CE27B5712900CA499D /* Result+Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4BF7CD27B5712900CA499D /* Result+Void.swift */; }; @@ -184,7 +172,8 @@ 3A4CE32C26A18D7300ECF460 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4CE32B26A18D7300ECF460 /* UserDefault.swift */; }; 3A4CE33026A18DFC00ECF460 /* GroupUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4CE32F26A18DFC00ECF460 /* GroupUserDefaults.swift */; }; 3A4CE33626A1A04000ECF460 /* SelectBaseNodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4CE33526A1A04000ECF460 /* SelectBaseNodeViewController.swift */; }; - 3A4D90A2273A728300A23FA8 /* WalletConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4D90A1273A728300A23FA8 /* WalletConnectivityManager.swift */; }; + 3A4D90A4273A75FC00A23FA8 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4D90A3273A75FC00A23FA8 /* Wallet.swift */; }; + 3A51B1AD28F00A4F0094FDD6 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A51B1AC28F00A4F0094FDD6 /* Logger.swift */; }; 3A5E881E26BA9CF10029BB8B /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5E881D26BA9CF10029BB8B /* HomeView.swift */; }; 3A62674626D76E6B007F9895 /* SelectNetworkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62674526D76E6B007F9895 /* SelectNetworkViewController.swift */; }; 3A62674A26D76EC4007F9895 /* SelectNetworkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62674926D76EC4007F9895 /* SelectNetworkModel.swift */; }; @@ -199,6 +188,11 @@ 3A6A033E2802DEEB000432B4 /* PopUpPresenter+CommonPopUps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6A033D2802DEEB000432B4 /* PopUpPresenter+CommonPopUps.swift */; }; 3A6CD3B5279194980034DF81 /* RequestTariAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6CD3B4279194980034DF81 /* RequestTariAmountView.swift */; }; 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */; }; + 3A70433D28E21CFB00207D6F /* CompletedTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A70433C28E21CFB00207D6F /* CompletedTransaction.swift */; }; + 3A70433F28E21D3600207D6F /* CompletedTransactionKernel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A70433E28E21D3600207D6F /* CompletedTransactionKernel.swift */; }; + 3A70434128E21D7A00207D6F /* PendingInboundTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A70434028E21D7A00207D6F /* PendingInboundTransaction.swift */; }; + 3A70434628E21DD200207D6F /* PendingOutboundTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A70434528E21DD200207D6F /* PendingOutboundTransaction.swift */; }; + 3A70434828E21F5600207D6F /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A70434728E21F5600207D6F /* Transaction.swift */; }; 3A7097CF27D0E29900641250 /* TariNetwork+Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7097CE27D0E29900641250 /* TariNetwork+Mocks.swift */; }; 3A72DC3D26DFA02900E0BC43 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A72DC3C26DFA02900E0BC43 /* NetworkManager.swift */; }; 3A72DC4126DFA1E100E0BC43 /* NetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A72DC4026DFA1E100E0BC43 /* NetworkSettings.swift */; }; @@ -210,22 +204,44 @@ 3A7A38D32881689A00D7E8BD /* PopUpNetworkStatusContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7A38D22881689A00D7E8BD /* PopUpNetworkStatusContentView.swift */; }; 3A7E06DA287831BD00D15190 /* UTXOsEstimationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7E06D9287831BD00D15190 /* UTXOsEstimationLabel.swift */; }; 3A7E06DC287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7E06DB287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift */; }; + 3A8005C628EAF0A50022A38A /* TransactionSendResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8005C528EAF0A50022A38A /* TransactionSendResult.swift */; }; + 3A8005C828EAF1AB0022A38A /* RestoreWalletStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8005C728EAF1AB0022A38A /* RestoreWalletStatus.swift */; }; + 3A8005CB28EAF3A90022A38A /* TransactionValidationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8005CA28EAF3A90022A38A /* TransactionValidationData.swift */; }; + 3A8005CD28EAF9110022A38A /* WalletBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8005CC28EAF9100022A38A /* WalletBalance.swift */; }; + 3A8005CF28EAF9380022A38A /* BaseNodeConnectivityStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */; }; 3A80F636287C56D60099EB73 /* PopUpUtxoDetailsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A80F635287C56D60099EB73 /* PopUpUtxoDetailsContentView.swift */; }; 3A82455427E1EDBB003B6B59 /* TransactionDetailsValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A82455327E1EDBB003B6B59 /* TransactionDetailsValueView.swift */; }; 3A82455627E1EE0D003B6B59 /* TransactionDetailsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A82455527E1EE0D003B6B59 /* TransactionDetailsSectionView.swift */; }; 3A82455827E1EE38003B6B59 /* TransactionDetailsEmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A82455727E1EE38003B6B59 /* TransactionDetailsEmojiView.swift */; }; 3A82455A27E1EE93003B6B59 /* TransactionDetailsContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A82455927E1EE93003B6B59 /* TransactionDetailsContactView.swift */; }; 3A82455C27E1EEE3003B6B59 /* TransactionDetailsNoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A82455B27E1EEE3003B6B59 /* TransactionDetailsNoteView.swift */; }; + 3A8473BF28EC51780015E63A /* TxoValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473BE28EC51780015E63A /* TxoValidationStatus.swift */; }; + 3A8473C228EC556C0015E63A /* CoreTariService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473C128EC556C0015E63A /* CoreTariService.swift */; }; + 3A8473C428EC55B10015E63A /* TariTransactionsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473C328EC55B10015E63A /* TariTransactionsService.swift */; }; + 3A8473C628EC55DB0015E63A /* TariContactsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473C528EC55DB0015E63A /* TariContactsService.swift */; }; + 3A8473C828EC55F50015E63A /* TariValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473C728EC55F50015E63A /* TariValidationService.swift */; }; + 3A8473CA28EC56180015E63A /* TariFeesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473C928EC56180015E63A /* TariFeesService.swift */; }; + 3A8473CC28EC562E0015E63A /* TariConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473CB28EC562E0015E63A /* TariConnectionService.swift */; }; + 3A8473CE28EC56480015E63A /* TariUTXOsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473CD28EC56480015E63A /* TariUTXOsService.swift */; }; + 3A8473D028EC56600015E63A /* TariRecoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473CF28EC56600015E63A /* TariRecoveryService.swift */; }; + 3A8473D228EC568A0015E63A /* TariFaucetService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473D128EC568A0015E63A /* TariFaucetService.swift */; }; + 3A8473D428EC56A90015E63A /* TariEncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473D328EC56A90015E63A /* TariEncryptionService.swift */; }; + 3A8473D628EC56C90015E63A /* TariBalanceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473D528EC56C90015E63A /* TariBalanceService.swift */; }; + 3A8473D828EC56EF0015E63A /* TariKeyValueService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473D728EC56EF0015E63A /* TariKeyValueService.swift */; }; + 3A8473DA28EC57AA0015E63A /* Publisher+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8473D928EC57AA0015E63A /* Publisher+Binding.swift */; }; 3A84CDD22846087D005F2F8D /* UTXOsWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A84CDD12846087D005F2F8D /* UTXOsWalletViewController.swift */; }; 3A84CDD42846088D005F2F8D /* UTXOsWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A84CDD32846088D005F2F8D /* UTXOsWalletView.swift */; }; 3A84CDD62846089B005F2F8D /* UTXOsWalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A84CDD52846089B005F2F8D /* UTXOsWalletModel.swift */; }; 3A84CDD828460967005F2F8D /* UTXOsWalletConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A84CDD728460967005F2F8D /* UTXOsWalletConstructor.swift */; }; 3A860BD2287DA2B800FC4F76 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A860BD1287DA2B800FC4F76 /* HomeViewModel.swift */; }; + 3A874038278842F500D80823 /* TorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A874037278842F500D80823 /* TorManager.swift */; }; + 3A87403A2788438E00D80823 /* FFIWalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8740392788438E00D80823 /* FFIWalletManager.swift */; }; 3A87C3B527A94086007A553F /* CoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A87C3B427A94086007A553F /* CoreError.swift */; }; 3A87C3B727A9409E007A553F /* WalletError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A87C3B627A9409E007A553F /* WalletError.swift */; }; 3A87C3B927A96423007A553F /* ErrorMessageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A87C3B827A96423007A553F /* ErrorMessageManagerTests.swift */; }; 3A87E57F28409D630099EA0E /* AddAmountSpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A87E57E28409D630099EA0E /* AddAmountSpinnerView.swift */; }; 3A8826C527F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8826C427F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift */; }; + 3A8A03EB28F587FD003EC8FE /* AppConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8A03EA28F587FD003EC8FE /* AppConfigurator.swift */; }; 3A8A05562853270B00D48FCE /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8A05552853270B00D48FCE /* UIImage+Utils.swift */; }; 3A8A0558285370B200D48FCE /* UTXOsWalletTextTickButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8A0557285370B200D48FCE /* UTXOsWalletTextTickButton.swift */; }; 3A93159B286C30AA0001660E /* LeftImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A93159A286C30AA0001660E /* LeftImageButton.swift */; }; @@ -245,12 +261,11 @@ 3AA2DD942796E8BA00DC3CF7 /* QRCodePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2DD932796E8BA00DC3CF7 /* QRCodePresentationController.swift */; }; 3AA2DD962796F72100DC3CF7 /* QRCodePresentationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2DD952796F72100DC3CF7 /* QRCodePresentationView.swift */; }; 3AA2E2212885755A00D30B62 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2E2202885755A00D30B62 /* NetworkMonitor.swift */; }; - 3AA2E2232885759A00D30B62 /* TorMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2E2222885759A00D30B62 /* TorMonitor.swift */; }; - 3AA2E225288576F600D30B62 /* BaseNodeStatusMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2E224288576F600D30B62 /* BaseNodeStatusMonitor.swift */; }; 3AA9499E26BD3EAC007C550D /* HomeViewToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA9499D26BD3EAC007C550D /* HomeViewToolbar.swift */; }; + 3AABFC9B28F5960100D87773 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AABFC9A28F5960100D87773 /* LogFormatter.swift */; }; + 3AABFC9D28F59B2200D87773 /* StatusLoggerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AABFC9C28F59B2200D87773 /* StatusLoggerManager.swift */; }; 3AB2172E272BE7D3007139F1 /* NetworkManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB2172D272BE7D3007139F1 /* NetworkManagerTests.swift */; }; 3AB21730272BEC7A007139F1 /* StringTokenizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB2172F272BEC7A007139F1 /* StringTokenizationTests.swift */; }; - 3AB21736272BEF5A007139F1 /* TariLibWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB21735272BEF59007139F1 /* TariLibWrapperTests.swift */; }; 3AB8B911285202D200E66D5C /* UTXOsWalletTileTickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB8B910285202D200E66D5C /* UTXOsWalletTileTickView.swift */; }; 3ABBBA7C2850771C00A3108D /* UTXOsWalletTextListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBBA7B2850771C00A3108D /* UTXOsWalletTextListView.swift */; }; 3ABBBA7E2850815C00A3108D /* UTXOsWalletTileListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBBA7D2850815C00A3108D /* UTXOsWalletTileListView.swift */; }; @@ -261,6 +276,8 @@ 3ABF91A9283FFA790001C766 /* AboutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF91A8283FFA790001C766 /* AboutModel.swift */; }; 3ABF91AC283FFAA50001C766 /* AboutViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF91AB283FFAA50001C766 /* AboutViewCell.swift */; }; 3AC05F9B26C3F302002742C6 /* TransactionsListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC05F9A26C3F302002742C6 /* TransactionsListHeaderView.swift */; }; + 3AC6F11228E9C6590068E6FF /* WalletCallbacksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6F11128E9C6590068E6FF /* WalletCallbacksManager.swift */; }; + 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */; }; 3AC877E4287415AD006F327B /* UTXOsWalletPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC877E3287415AD006F327B /* UTXOsWalletPlaceholderView.swift */; }; 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC877E528741680006F327B /* UTXOsWalletLoadingView.swift */; }; 3AC8C25D27999CAC00334F26 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC8C25C27999CAC00334F26 /* PageViewController.swift */; }; @@ -310,21 +327,23 @@ 3AEDBE5127CE477B006B0166 /* DeepLinkDataEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEDBE5027CE477B006B0166 /* DeepLinkDataEncoder.swift */; }; 3AEDBE5927CE4F80006B0166 /* DeepLinkFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEDBE5827CE4F80006B0166 /* DeepLinkFormatterTests.swift */; }; 3AEE7AA3286599E6000F84ED /* UTXOsWalletTopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEE7AA2286599E6000F84ED /* UTXOsWalletTopBar.swift */; }; - 3AF167442782FD7C0019A30E /* Wallet+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF167432782FD7C0019A30E /* Wallet+Conveniences.swift */; }; + 3AF335E028D7983100B48F33 /* MessageMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF335DF28D7983000B48F33 /* MessageMetadata.swift */; }; 3AF51819270D7EFC00746884 /* AddRecipientModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF51818270D7EFC00746884 /* AddRecipientModel.swift */; }; 3AF53B7E27311D1400C78562 /* SeedWordsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF53B7D27311D1400C78562 /* SeedWordsTests.swift */; }; 3AF53B8127311D9800C78562 /* MobileWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF53B8027311D9800C78562 /* MobileWalletTests.swift */; }; + 3AF56BBB28B774C0004E8E43 /* PendingInboundTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF56BBA28B774C0004E8E43 /* PendingInboundTransactions.swift */; }; + 3AF56BBD28B77969004E8E43 /* PendingOutboundTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF56BBC28B77969004E8E43 /* PendingOutboundTransactions.swift */; }; 3AF79D5E2727201A00613C24 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF79D5D2727201A00613C24 /* ScrollableLabel.swift */; }; 3AF79D602727206200613C24 /* AddRecipientSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF79D5F2727206200613C24 /* AddRecipientSearchView.swift */; }; 3AF97E6527CF767D00FF6A3F /* TransactionsSendDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF97E6427CF767D00FF6A3F /* TransactionsSendDeeplink.swift */; }; 3AF97E6727CF769A00FF6A3F /* BaseNodesAddDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF97E6627CF769A00FF6A3F /* BaseNodesAddDeeplink.swift */; }; 3AF97E6927CF76C800FF6A3F /* DeeplinkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF97E6827CF76C800FF6A3F /* DeeplinkHandler.swift */; }; - 3AF97E6C27CF988D00FF6A3F /* BaseNodeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF97E6B27CF988D00FF6A3F /* BaseNodeManager.swift */; }; 3AF97E7027CFCA2000FF6A3F /* ShortcutsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF97E6F27CFCA2000FF6A3F /* ShortcutsManager.swift */; }; 3AF985FA286B233100290387 /* PopUpSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF985F9286B233100290387 /* PopUpSelectionView.swift */; }; 3AFAC360271C6B4D008AA842 /* ContactAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFAC35F271C6B4D008AA842 /* ContactAvatarView.swift */; }; 3AFAC362271C6F9E008AA842 /* UITextField+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFAC361271C6F9E008AA842 /* UITextField+Combine.swift */; }; 3AFAEBF226B15A3300B82603 /* KeyboardAvoidingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFAEBF126B15A3300B82603 /* KeyboardAvoidingContentView.swift */; }; + 3AFC3D60288EC3FA0046D386 /* UnselectableTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFC3D5F288EC3FA0046D386 /* UnselectableTextView.swift */; }; 3AFC6E032745051400A20287 /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFC6E022745051400A20287 /* SettingsHeaderView.swift */; }; 491AB8A023F3FF4400372189 /* AddAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491AB89F23F3FF4400372189 /* AddAmountViewController.swift */; }; 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49996E1823F164BA002B6696 /* AnimatedBalanceLabel.swift */; }; @@ -333,7 +352,6 @@ 4CCF87ED23E8273900C8CDB1 /* libtari_wallet_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CCF87EC23E8273900C8CDB1 /* libtari_wallet_ffi.a */; }; 4CD20A362407967B007B64D8 /* TransportConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD20A352407967B007B64D8 /* TransportConfig.swift */; }; 4CDEC323273A5E3600999DCB /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDEC322273A5E3500999DCB /* Balance.swift */; }; - 4CF0911B26E17CEE00582972 /* CompletedTxKernel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0911A26E17CEE00582972 /* CompletedTxKernel.swift */; }; 6191948FA1B3F223B37A0D9B /* UTXO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619190C6EBD050DC647D1D7B /* UTXO.swift */; }; A01F54A8255EAB7E00F49AFA /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01F54A7255EAB7E00F49AFA /* Localization.swift */; }; A0779C612552C1AF00614EF3 /* DeleteWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0779C602552C1AF00614EF3 /* DeleteWalletViewController.swift */; }; @@ -392,24 +410,16 @@ 001F6CDB238011CA00FA7002 /* PublicKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKey.swift; sourceTree = ""; }; 001F6CDF2380198D00FA7002 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; 001F6CE32380253B00FA7002 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; - 001F6CE72381A22E00FA7002 /* CompletedTxs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTxs.swift; sourceTree = ""; }; - 001F6CEB2381A39300FA7002 /* CompletedTx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTx.swift; sourceTree = ""; }; 00230AF92376C6DD00F23E34 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; 00230B0823770CEA00F23E34 /* CALayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = ""; }; 00262EB3236F024400A6C8A0 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 00325EC9239E4A1000F76918 /* FadedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadedOverlayView.swift; sourceTree = ""; }; - 00369D802426080D00BAB95B /* TariLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariLogger.swift; sourceTree = ""; }; 0039B1B123FA925500F91E0F /* PasteEmojisView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteEmojisView.swift; sourceTree = ""; }; 004277F223E0407900AE7BD9 /* TextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextButton.swift; sourceTree = ""; }; 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Content.swift"; sourceTree = ""; }; - 004277FA23E1A61F00AE7BD9 /* ErrorDescriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDescriptions.swift; sourceTree = ""; }; 004277FE23E2F1D700AE7BD9 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 0042780223E8421100AE7BD9 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 0048AF42245ADCA300E1A359 /* env.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = env.json; sourceTree = SOURCE_ROOT; }; - 004997AA2382E893000A0B7D /* PendingOutboundTxs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingOutboundTxs.swift; sourceTree = ""; }; - 004997AE2382E938000A0B7D /* PendingOutboundTx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingOutboundTx.swift; sourceTree = ""; }; - 004997B22382ED68000A0B7D /* PendingInboundTx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInboundTx.swift; sourceTree = ""; }; - 004997B62382ED79000A0B7D /* PendingInboundTxs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInboundTxs.swift; sourceTree = ""; }; 005387232405136700901A68 /* AddNoteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddNoteViewController.swift; sourceTree = ""; }; 0053872F24065F6C00901A68 /* SlideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideView.swift; sourceTree = ""; }; 00665E7424E422C500030A13 /* CustomTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBar.swift; sourceTree = ""; }; @@ -420,18 +430,11 @@ 0087A1A523F4235400B89EE7 /* ScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; 0087A1A723F4235400B89EE7 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; 0087A1A823F4235400B89EE7 /* AddRecipientViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipientViewController.swift; sourceTree = ""; }; - 0091D4A623ED879E004BF7F7 /* Signature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signature.swift; sourceTree = ""; }; 0091D4AA23ED87BE004BF7F7 /* KeyServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServer.swift; sourceTree = ""; }; - 00947CC123CF090D00526DD5 /* TxProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxProtocol.swift; sourceTree = ""; }; - 00947CC623D1A35200526DD5 /* TxsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxsProtocol.swift; sourceTree = ""; }; - 009BF8AB237ABB4700C02E44 /* TariLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariLib.swift; sourceTree = ""; }; 009DFCA62473E33F0061DBC9 /* DeepLinkParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkParams.swift; sourceTree = ""; }; - 009DFCB024741F9B0061DBC9 /* WalletTxs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTxs.swift; sourceTree = ""; }; - 00A057AE23D875840029C22A /* TariEventBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariEventBus.swift; sourceTree = ""; }; 00A6651E2437AD9A0046E730 /* SplashAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SplashAnimation.json; sourceTree = ""; }; 00A66522243B3E380046E730 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 00A66526243C766D0046E730 /* ConnectionMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionMonitor.swift; sourceTree = ""; }; - 00A9190A242CDD7700B7F14D /* Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tracker.swift; sourceTree = ""; }; 00A994322396434B007D9990 /* PulseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulseButton.swift; sourceTree = ""; }; 00B084BF249E471800F7B9BD /* EmojiSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiSet.swift; sourceTree = ""; }; 00B084C4249E4F6900F7B9BD /* SeedWords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedWords.swift; sourceTree = ""; }; @@ -444,12 +447,8 @@ 00BBD808237D80C800EBF5E6 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ByteVector.swift; sourceTree = ""; }; 00BBD811237E9EE200EBF5E6 /* PrivateKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKey.swift; sourceTree = ""; }; - 00BBD815237EA67600EBF5E6 /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = ""; }; 00BBD819237EB31900EBF5E6 /* CommsConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommsConfig.swift; sourceTree = ""; }; - 00BCE47D240D5A9700B181F3 /* OnionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionManager.swift; sourceTree = ""; }; - 00BCE47E240D5A9700B181F3 /* OnionConnector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionConnector.swift; sourceTree = ""; }; 00BCE486240D5C4100B181F3 /* Tor.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Tor.framework; path = Carthage/Build/iOS/Tor.framework; sourceTree = ""; }; - 00BCE488240EB1EE00B181F3 /* SplashViewControllerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewControllerSetup.swift; sourceTree = ""; }; 00C185B8238943980096984E /* UIViewControllerDebugExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerDebugExtension.swift; sourceTree = ""; }; 00C185BD238953F60096984E /* DebugLogsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLogsTableViewController.swift; sourceTree = ""; }; 00C520AE2469716E0077BF7F /* libc++.1.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.1.tbd"; path = "usr/lib/libc++.1.tbd"; sourceTree = SDKROOT; }; @@ -537,6 +536,8 @@ 3A0124A427B3EDEB00A481F4 /* TransactionViewControllable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewControllable.swift; sourceTree = ""; }; 3A03682D27DF20E300F304A2 /* TransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsViewController.swift; sourceTree = ""; }; 3A03683027DF211D00F304A2 /* TransactionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsView.swift; sourceTree = ""; }; + 3A03A5FF288932DE00788A02 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; + 3A03A601288962FA00788A02 /* SplashViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModel.swift; sourceTree = ""; }; 3A052BF327B2658500C93671 /* AppVersionFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionFormatter.swift; sourceTree = ""; }; 3A052BF927B2940900C93671 /* WalletTransactionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionsManager.swift; sourceTree = ""; }; 3A0EA97926AA8E1C002612D4 /* TokenCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenCollectionView.swift; sourceTree = ""; }; @@ -555,6 +556,10 @@ 3A2B956928648D860085BE21 /* CChar+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CChar+Helpers.swift"; sourceTree = ""; }; 3A2C0B8627DA22970018C5A8 /* SeedWordListElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedWordListElementView.swift; sourceTree = ""; }; 3A2C0B8827DA22C20018C5A8 /* ExpandButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandButton.swift; sourceTree = ""; }; + 3A2F30B328F5940A0095E25D /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; }; + 3A2F30B528F594520095E25D /* FileLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogger.swift; sourceTree = ""; }; + 3A2F30B728F5946B0095E25D /* CrashLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashLogger.swift; sourceTree = ""; }; + 3A312FED28B6316D00A290D3 /* CompletedTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTransactions.swift; sourceTree = ""; }; 3A35412B26A72D9F002AB5A8 /* ViewIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifiable.swift; sourceTree = ""; }; 3A35413026A72DEE002AB5A8 /* SelectBaseNodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectBaseNodeCell.swift; sourceTree = ""; }; 3A35413526A738D4002AB5A8 /* RoundedInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedInputField.swift; sourceTree = ""; }; @@ -572,6 +577,7 @@ 3A4376E4269C0C10006107B0 /* RestoreWalletFromSeedsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletFromSeedsViewController.swift; sourceTree = ""; }; 3A4376E8269C0C58006107B0 /* RestoreWalletFromSeedsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletFromSeedsModel.swift; sourceTree = ""; }; 3A4376ED269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletFromSeedsView.swift; sourceTree = ""; }; + 3A47ABCC27478208007F351B /* Tari.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tari.swift; sourceTree = ""; }; 3A4949A4283FD603002768AE /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 3A494A412716D6DA00CF5B05 /* AddRecipientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipientView.swift; sourceTree = ""; }; 3A4BF7CD27B5712900CA499D /* Result+Void.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Void.swift"; sourceTree = ""; }; @@ -579,7 +585,8 @@ 3A4CE32B26A18D7300ECF460 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; 3A4CE32F26A18DFC00ECF460 /* GroupUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUserDefaults.swift; sourceTree = ""; }; 3A4CE33526A1A04000ECF460 /* SelectBaseNodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectBaseNodeViewController.swift; sourceTree = ""; }; - 3A4D90A1273A728300A23FA8 /* WalletConnectivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectivityManager.swift; sourceTree = ""; }; + 3A4D90A3273A75FC00A23FA8 /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = ""; }; + 3A51B1AC28F00A4F0094FDD6 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3A5E881D26BA9CF10029BB8B /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 3A62674526D76E6B007F9895 /* SelectNetworkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectNetworkViewController.swift; sourceTree = ""; }; 3A62674926D76EC4007F9895 /* SelectNetworkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectNetworkModel.swift; sourceTree = ""; }; @@ -594,6 +601,11 @@ 3A6A033D2802DEEB000432B4 /* PopUpPresenter+CommonPopUps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PopUpPresenter+CommonPopUps.swift"; sourceTree = ""; }; 3A6CD3B4279194980034DF81 /* RequestTariAmountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTariAmountView.swift; sourceTree = ""; }; 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariFeePerGramStats.swift; sourceTree = ""; }; + 3A70433C28E21CFB00207D6F /* CompletedTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTransaction.swift; sourceTree = ""; }; + 3A70433E28E21D3600207D6F /* CompletedTransactionKernel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTransactionKernel.swift; sourceTree = ""; }; + 3A70434028E21D7A00207D6F /* PendingInboundTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInboundTransaction.swift; sourceTree = ""; }; + 3A70434528E21DD200207D6F /* PendingOutboundTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingOutboundTransaction.swift; sourceTree = ""; }; + 3A70434728E21F5600207D6F /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = ""; }; 3A7097CE27D0E29900641250 /* TariNetwork+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TariNetwork+Mocks.swift"; sourceTree = ""; }; 3A72DC3C26DFA02900E0BC43 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; 3A72DC4026DFA1E100E0BC43 /* NetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettings.swift; sourceTree = ""; }; @@ -605,22 +617,44 @@ 3A7A38D22881689A00D7E8BD /* PopUpNetworkStatusContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpNetworkStatusContentView.swift; sourceTree = ""; }; 3A7E06D9287831BD00D15190 /* UTXOsEstimationLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsEstimationLabel.swift; sourceTree = ""; }; 3A7E06DB287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpCombineUTXOsConfirmationContentView.swift; sourceTree = ""; }; + 3A8005C528EAF0A50022A38A /* TransactionSendResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSendResult.swift; sourceTree = ""; }; + 3A8005C728EAF1AB0022A38A /* RestoreWalletStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletStatus.swift; sourceTree = ""; }; + 3A8005CA28EAF3A90022A38A /* TransactionValidationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionValidationData.swift; sourceTree = ""; }; + 3A8005CC28EAF9100022A38A /* WalletBalance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletBalance.swift; sourceTree = ""; }; + 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeConnectivityStatus.swift; sourceTree = ""; }; 3A80F635287C56D60099EB73 /* PopUpUtxoDetailsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpUtxoDetailsContentView.swift; sourceTree = ""; }; 3A82455327E1EDBB003B6B59 /* TransactionDetailsValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsValueView.swift; sourceTree = ""; }; 3A82455527E1EE0D003B6B59 /* TransactionDetailsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsSectionView.swift; sourceTree = ""; }; 3A82455727E1EE38003B6B59 /* TransactionDetailsEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsEmojiView.swift; sourceTree = ""; }; 3A82455927E1EE93003B6B59 /* TransactionDetailsContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsContactView.swift; sourceTree = ""; }; 3A82455B27E1EEE3003B6B59 /* TransactionDetailsNoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsNoteView.swift; sourceTree = ""; }; + 3A8473BE28EC51780015E63A /* TxoValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxoValidationStatus.swift; sourceTree = ""; }; + 3A8473C128EC556C0015E63A /* CoreTariService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTariService.swift; sourceTree = ""; }; + 3A8473C328EC55B10015E63A /* TariTransactionsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariTransactionsService.swift; sourceTree = ""; }; + 3A8473C528EC55DB0015E63A /* TariContactsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariContactsService.swift; sourceTree = ""; }; + 3A8473C728EC55F50015E63A /* TariValidationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariValidationService.swift; sourceTree = ""; }; + 3A8473C928EC56180015E63A /* TariFeesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariFeesService.swift; sourceTree = ""; }; + 3A8473CB28EC562E0015E63A /* TariConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariConnectionService.swift; sourceTree = ""; }; + 3A8473CD28EC56480015E63A /* TariUTXOsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariUTXOsService.swift; sourceTree = ""; }; + 3A8473CF28EC56600015E63A /* TariRecoveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariRecoveryService.swift; sourceTree = ""; }; + 3A8473D128EC568A0015E63A /* TariFaucetService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariFaucetService.swift; sourceTree = ""; }; + 3A8473D328EC56A90015E63A /* TariEncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariEncryptionService.swift; sourceTree = ""; }; + 3A8473D528EC56C90015E63A /* TariBalanceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariBalanceService.swift; sourceTree = ""; }; + 3A8473D728EC56EF0015E63A /* TariKeyValueService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TariKeyValueService.swift; sourceTree = ""; }; + 3A8473D928EC57AA0015E63A /* Publisher+Binding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Binding.swift"; sourceTree = ""; }; 3A84CDD12846087D005F2F8D /* UTXOsWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletViewController.swift; sourceTree = ""; }; 3A84CDD32846088D005F2F8D /* UTXOsWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletView.swift; sourceTree = ""; }; 3A84CDD52846089B005F2F8D /* UTXOsWalletModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletModel.swift; sourceTree = ""; }; 3A84CDD728460967005F2F8D /* UTXOsWalletConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletConstructor.swift; sourceTree = ""; }; 3A860BD1287DA2B800FC4F76 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + 3A874037278842F500D80823 /* TorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorManager.swift; sourceTree = ""; }; + 3A8740392788438E00D80823 /* FFIWalletManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFIWalletManager.swift; sourceTree = ""; }; 3A87C3B427A94086007A553F /* CoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreError.swift; sourceTree = ""; }; 3A87C3B627A9409E007A553F /* WalletError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletError.swift; sourceTree = ""; }; 3A87C3B827A96423007A553F /* ErrorMessageManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageManagerTests.swift; sourceTree = ""; }; 3A87E57E28409D630099EA0E /* AddAmountSpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAmountSpinnerView.swift; sourceTree = ""; }; 3A8826C427F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewDiffableDataSource+Animation.swift"; sourceTree = ""; }; + 3A8A03EA28F587FD003EC8FE /* AppConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurator.swift; sourceTree = ""; }; 3A8A05552853270B00D48FCE /* UIImage+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; 3A8A0557285370B200D48FCE /* UTXOsWalletTextTickButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTextTickButton.swift; sourceTree = ""; }; 3A93159A286C30AA0001660E /* LeftImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftImageButton.swift; sourceTree = ""; }; @@ -640,12 +674,11 @@ 3AA2DD932796E8BA00DC3CF7 /* QRCodePresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodePresentationController.swift; sourceTree = ""; }; 3AA2DD952796F72100DC3CF7 /* QRCodePresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodePresentationView.swift; sourceTree = ""; }; 3AA2E2202885755A00D30B62 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; - 3AA2E2222885759A00D30B62 /* TorMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorMonitor.swift; sourceTree = ""; }; - 3AA2E224288576F600D30B62 /* BaseNodeStatusMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeStatusMonitor.swift; sourceTree = ""; }; 3AA9499D26BD3EAC007C550D /* HomeViewToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewToolbar.swift; sourceTree = ""; }; + 3AABFC9A28F5960100D87773 /* LogFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = ""; }; + 3AABFC9C28F59B2200D87773 /* StatusLoggerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusLoggerManager.swift; sourceTree = ""; }; 3AB2172D272BE7D3007139F1 /* NetworkManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManagerTests.swift; sourceTree = ""; }; 3AB2172F272BEC7A007139F1 /* StringTokenizationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringTokenizationTests.swift; sourceTree = ""; }; - 3AB21735272BEF59007139F1 /* TariLibWrapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TariLibWrapperTests.swift; sourceTree = ""; }; 3AB8B910285202D200E66D5C /* UTXOsWalletTileTickView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTileTickView.swift; sourceTree = ""; }; 3ABBBA7B2850771C00A3108D /* UTXOsWalletTextListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTextListView.swift; sourceTree = ""; }; 3ABBBA7D2850815C00A3108D /* UTXOsWalletTileListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTileListView.swift; sourceTree = ""; }; @@ -656,6 +689,8 @@ 3ABF91A8283FFA790001C766 /* AboutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutModel.swift; sourceTree = ""; }; 3ABF91AB283FFAA50001C766 /* AboutViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCell.swift; sourceTree = ""; }; 3AC05F9A26C3F302002742C6 /* TransactionsListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsListHeaderView.swift; sourceTree = ""; }; + 3AC6F11128E9C6590068E6FF /* WalletCallbacksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletCallbacksManager.swift; sourceTree = ""; }; + 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBridgesHandable.swift; sourceTree = ""; }; 3AC877E3287415AD006F327B /* UTXOsWalletPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletPlaceholderView.swift; sourceTree = ""; }; 3AC877E528741680006F327B /* UTXOsWalletLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletLoadingView.swift; sourceTree = ""; }; 3AC8C25C27999CAC00334F26 /* PageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; @@ -705,21 +740,23 @@ 3AEDBE5027CE477B006B0166 /* DeepLinkDataEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDataEncoder.swift; sourceTree = ""; }; 3AEDBE5827CE4F80006B0166 /* DeepLinkFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkFormatterTests.swift; sourceTree = ""; }; 3AEE7AA2286599E6000F84ED /* UTXOsWalletTopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOsWalletTopBar.swift; sourceTree = ""; }; - 3AF167432782FD7C0019A30E /* Wallet+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Wallet+Conveniences.swift"; sourceTree = ""; }; + 3AF335DF28D7983000B48F33 /* MessageMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageMetadata.swift; sourceTree = ""; }; 3AF51818270D7EFC00746884 /* AddRecipientModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipientModel.swift; sourceTree = ""; }; 3AF53B7D27311D1400C78562 /* SeedWordsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedWordsTests.swift; sourceTree = ""; }; 3AF53B8027311D9800C78562 /* MobileWalletTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MobileWalletTests.swift; sourceTree = ""; }; + 3AF56BBA28B774C0004E8E43 /* PendingInboundTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInboundTransactions.swift; sourceTree = ""; }; + 3AF56BBC28B77969004E8E43 /* PendingOutboundTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingOutboundTransactions.swift; sourceTree = ""; }; 3AF79D5D2727201A00613C24 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = ""; }; 3AF79D5F2727206200613C24 /* AddRecipientSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipientSearchView.swift; sourceTree = ""; }; 3AF97E6427CF767D00FF6A3F /* TransactionsSendDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsSendDeeplink.swift; sourceTree = ""; }; 3AF97E6627CF769A00FF6A3F /* BaseNodesAddDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodesAddDeeplink.swift; sourceTree = ""; }; 3AF97E6827CF76C800FF6A3F /* DeeplinkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkHandler.swift; sourceTree = ""; }; - 3AF97E6B27CF988D00FF6A3F /* BaseNodeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNodeManager.swift; sourceTree = ""; }; 3AF97E6F27CFCA2000FF6A3F /* ShortcutsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsManager.swift; sourceTree = ""; }; 3AF985F9286B233100290387 /* PopUpSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpSelectionView.swift; sourceTree = ""; }; 3AFAC35F271C6B4D008AA842 /* ContactAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAvatarView.swift; sourceTree = ""; }; 3AFAC361271C6F9E008AA842 /* UITextField+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Combine.swift"; sourceTree = ""; }; 3AFAEBF126B15A3300B82603 /* KeyboardAvoidingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardAvoidingContentView.swift; sourceTree = ""; }; + 3AFC3D5F288EC3FA0046D386 /* UnselectableTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnselectableTextView.swift; sourceTree = ""; }; 3AFC6E022745051400A20287 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; 491AB89F23F3FF4400372189 /* AddAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAmountViewController.swift; sourceTree = ""; }; 49996E1823F164BA002B6696 /* AnimatedBalanceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedBalanceLabel.swift; sourceTree = ""; }; @@ -731,7 +768,6 @@ 4CCF87EC23E8273900C8CDB1 /* libtari_wallet_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtari_wallet_ffi.a; path = MobileWallet/TariLib/libtari_wallet_ffi.a; sourceTree = ""; }; 4CD20A352407967B007B64D8 /* TransportConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportConfig.swift; sourceTree = ""; }; 4CDEC322273A5E3500999DCB /* Balance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Balance.swift; sourceTree = ""; }; - 4CF0911A26E17CEE00582972 /* CompletedTxKernel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedTxKernel.swift; sourceTree = ""; }; 540C8A02EEFE26A1133C9AA7 /* Pods-MobileWallet-MobileWalletUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobileWallet-MobileWalletUITests.debug.xcconfig"; path = "Target Support Files/Pods-MobileWallet-MobileWalletUITests/Pods-MobileWallet-MobileWalletUITests.debug.xcconfig"; sourceTree = ""; }; 619190C6EBD050DC647D1D7B /* UTXO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTXO.swift; sourceTree = ""; }; 867662CACBB1C47B665EBA55 /* Pods-MobileWalletTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobileWalletTests.release.xcconfig"; path = "Target Support Files/Pods-MobileWalletTests/Pods-MobileWalletTests.release.xcconfig"; sourceTree = ""; }; @@ -800,8 +836,9 @@ 0012562D2371D21E00A9C067 /* Splash */ = { isa = PBXGroup; children = ( + 3A03A601288962FA00788A02 /* SplashViewModel.swift */, 0012562E2371D81500A9C067 /* SplashViewController.swift */, - 00BCE488240EB1EE00B181F3 /* SplashViewControllerSetup.swift */, + 3A03A5FF288932DE00788A02 /* SplashView.swift */, BFE1FBFF23E20E3600BA5EEC /* WalletCreationViewController.swift */, BF191A2D23E5B05400D33C85 /* CheckMarkAnimation */, BF191A2C23E5B02F00D33C85 /* EmojiWheelAnimation */, @@ -824,21 +861,6 @@ path = Boards; sourceTree = ""; }; - 001F6CEF2381A43800FA7002 /* Transactions */ = { - isa = PBXGroup; - children = ( - 4CF0911A26E17CEE00582972 /* CompletedTxKernel.swift */, - 001F6CEB2381A39300FA7002 /* CompletedTx.swift */, - 001F6CE72381A22E00FA7002 /* CompletedTxs.swift */, - 004997B22382ED68000A0B7D /* PendingInboundTx.swift */, - 004997B62382ED79000A0B7D /* PendingInboundTxs.swift */, - 004997AE2382E938000A0B7D /* PendingOutboundTx.swift */, - 004997AA2382E893000A0B7D /* PendingOutboundTxs.swift */, - 00947CC523D1A33900526DD5 /* Protocols */, - ); - path = Transactions; - sourceTree = ""; - }; 00262EAD236F013500A6C8A0 /* Extensions */ = { isa = PBXGroup; children = ( @@ -861,7 +883,6 @@ 37547D5524601BF600EB59CC /* UIView+GlobalFrame.swift */, 004277F623E0628A00AE7BD9 /* UIViewController+Content.swift */, 3AFAC361271C6F9E008AA842 /* UITextField+Combine.swift */, - 3AF167432782FD7C0019A30E /* Wallet+Conveniences.swift */, 3A420596279803DF00A8D49C /* CharacterSet+CustomSets.swift */, 3A4BF7CD27B5712900CA499D /* Result+Void.swift */, 3A8826C427F1B8320037F779 /* UICollectionViewDiffableDataSource+Animation.swift */, @@ -870,6 +891,7 @@ 3A8A05552853270B00D48FCE /* UIImage+Utils.swift */, 3A2B956928648D860085BE21 /* CChar+Helpers.swift */, 3ACC4F5F2876D2F100632F64 /* NSAttributedString+Format.swift */, + 3A8473D928EC57AA0015E63A /* Publisher+Binding.swift */, ); path = Extensions; sourceTree = ""; @@ -890,7 +912,6 @@ 37B5289324CB1E16008C80EB /* NetworkSpeedProvider.swift */, 00B4D6F9241B910200ED8318 /* NotificationManager.swift */, 00262EB3236F024400A6C8A0 /* Theme.swift */, - 00A9190A242CDD7700B7F14D /* Tracker.swift */, 372AECAA24B4A293005CBA0F /* UserDefaultsWrapper.swift */, 37AFE263245193CA006EA270 /* AlwaysPoppableNavigationController */, 00B4D6F4241B777500ED8318 /* BackgroundTasks */, @@ -919,23 +940,18 @@ 006D211B23CEDEEE007D1C10 /* Utils */ = { isa = PBXGroup; children = ( + 3A2F30B228F593EF0095E25D /* Loggers */, + 3A9D6C2A28E4604D00B14F7C /* Faucet */, + 0091D4AA23ED87BE004BF7F7 /* KeyServer.swift */, + 619190C6EBD050DC647D1D7B /* UTXO.swift */, 3AA2E21F2885754500D30B62 /* Connection Monitor */, 3ADA73DA270A029B00D50E3B /* Settings */, 3A72DC3B26DF9FF700E0BC43 /* Network */, 009DFCA62473E33F0061DBC9 /* DeepLinkParams.swift */, - 004277FA23E1A61F00AE7BD9 /* ErrorDescriptions.swift */, - 0091D4AA23ED87BE004BF7F7 /* KeyServer.swift */, 006D211C23CEDF16007D1C10 /* MicroTari.swift */, 00230AF92376C6DD00F23E34 /* Sequence.swift */, - 0091D4A623ED879E004BF7F7 /* Signature.swift */, 00F53A632428E44700448466 /* StorageCleanup.swift */, - 00A057AE23D875840029C22A /* TariEventBus.swift */, - 00369D802426080D00BAB95B /* TariLogger.swift */, - 619190C6EBD050DC647D1D7B /* UTXO.swift */, 3ACFDD1C26E8C1A900C5E1EA /* AppInfo.swift */, - 3A4D90A1273A728300A23FA8 /* WalletConnectivityManager.swift */, - 3A74E7DA273BCFF000F290D2 /* SeedWordsMnemonicWordList.swift */, - 3AF97E6B27CF988D00FF6A3F /* BaseNodeManager.swift */, ); name = Utils; path = Wrappers/Utils; @@ -966,15 +982,6 @@ path = AddRecipient; sourceTree = ""; }; - 00947CC523D1A33900526DD5 /* Protocols */ = { - isa = PBXGroup; - children = ( - 00947CC123CF090D00526DD5 /* TxProtocol.swift */, - 00947CC623D1A35200526DD5 /* TxsProtocol.swift */, - ); - path = Protocols; - sourceTree = ""; - }; 00A6779C23743674001A853E /* Transaction Details */ = { isa = PBXGroup; children = ( @@ -1006,39 +1013,6 @@ path = AppEntry; sourceTree = ""; }; - 00BBD80C237DA04300EBF5E6 /* Wrappers */ = { - isa = PBXGroup; - children = ( - 4CDEC322273A5E3500999DCB /* Balance.swift */, - 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */, - 00BBD819237EB31900EBF5E6 /* CommsConfig.swift */, - 001F6CE32380253B00FA7002 /* Contact.swift */, - 001F6CDF2380198D00FA7002 /* Contacts.swift */, - 00B084BF249E471800F7B9BD /* EmojiSet.swift */, - 00BBD811237E9EE200EBF5E6 /* PrivateKey.swift */, - 001F6CDB238011CA00FA7002 /* PublicKey.swift */, - 00B084C4249E4F6900F7B9BD /* SeedWords.swift */, - 4CD20A352407967B007B64D8 /* TransportConfig.swift */, - 00BBD815237EA67600EBF5E6 /* Wallet.swift */, - 009DFCB024741F9B0061DBC9 /* WalletTxs.swift */, - 001F6CEF2381A43800FA7002 /* Transactions */, - 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */, - 3A78860D2872FB39003B1F9A /* TariVectorWrapper.swift */, - ); - path = Wrappers; - sourceTree = ""; - }; - 00BCE485240D5A9E00B181F3 /* Tor */ = { - isa = PBXGroup; - children = ( - 00BCE47E240D5A9700B181F3 /* OnionConnector.swift */, - 00BCE47D240D5A9700B181F3 /* OnionManager.swift */, - 370E886224FE974100576F61 /* OnionSettings.swift */, - 370E886A24FE9FF800576F61 /* Helpers */, - ); - path = Tor; - sourceTree = ""; - }; 00C185BC238953B40096984E /* Debug */ = { isa = PBXGroup; children = ( @@ -1088,6 +1062,7 @@ 3A1325FF27EDE39F00289C8C /* Transaction */, 371A081624728FCB00F97713 /* TransitionUILabel */, 375AFDCD2475380B00C62CA1 /* WebBrowserView */, + 3AFC3D5F288EC3FA0046D386 /* UnselectableTextView.swift */, ); path = UIElements; sourceTree = ""; @@ -1223,6 +1198,7 @@ 3708D749247FCF2300807D72 /* SettingsViewController.swift */, A0779C5F2552C19500614EF3 /* AdvancedSettings */, 3708D750247FF7AD00807D72 /* BackUpSettings */, + 3AC6F11328E9D8840068E6FF /* CustomBridgesHandable.swift */, ); path = Settings; sourceTree = ""; @@ -1421,6 +1397,53 @@ path = Views; sourceTree = ""; }; + 3A2F30B228F593EF0095E25D /* Loggers */ = { + isa = PBXGroup; + children = ( + 3A51B1AC28F00A4F0094FDD6 /* Logger.swift */, + 3A2F30B328F5940A0095E25D /* ConsoleLogger.swift */, + 3A2F30B528F594520095E25D /* FileLogger.swift */, + 3A2F30B728F5946B0095E25D /* CrashLogger.swift */, + 3AABFC9A28F5960100D87773 /* LogFormatter.swift */, + 3AABFC9C28F59B2200D87773 /* StatusLoggerManager.swift */, + ); + path = Loggers; + sourceTree = ""; + }; + 3A312FEB28B6314A00A290D3 /* FFI */ = { + isa = PBXGroup; + children = ( + 4CDEC322273A5E3500999DCB /* Balance.swift */, + 3A8005CE28EAF9380022A38A /* BaseNodeConnectivityStatus.swift */, + 00BBD80D237DA07400EBF5E6 /* ByteVector.swift */, + 00B084BF249E471800F7B9BD /* EmojiSet.swift */, + 3A8005C728EAF1AB0022A38A /* RestoreWalletStatus.swift */, + 3A6F3FF7283BF980005D1793 /* TariFeePerGramStats.swift */, + 3A78860D2872FB39003B1F9A /* TariVectorWrapper.swift */, + 3A8005CA28EAF3A90022A38A /* TransactionValidationData.swift */, + 3A4D90A3273A75FC00A23FA8 /* Wallet.swift */, + 3AF335DD28D7905C00B48F33 /* Configs */, + 3AF335DA28D754EC00B48F33 /* Contacts */, + 3AF335DC28D78D1F00B48F33 /* Keys */, + 3AF335DB28D789F000B48F33 /* Seed Words */, + 3A312FEC28B6315A00A290D3 /* Transactions */, + 3A8473BE28EC51780015E63A /* TxoValidationStatus.swift */, + ); + path = FFI; + sourceTree = ""; + }; + 3A312FEC28B6315A00A290D3 /* Transactions */ = { + isa = PBXGroup; + children = ( + 3A70434328E21D9F00207D6F /* Pending Inbound */, + 3A70434428E21DB900207D6F /* Pending Outbound */, + 3A70434228E21D9400207D6F /* Completed */, + 3A70434728E21F5600207D6F /* Transaction.swift */, + 3A8005C528EAF0A50022A38A /* TransactionSendResult.swift */, + ); + path = Transactions; + sourceTree = ""; + }; 3A35412F26A72DDF002AB5A8 /* Views */ = { isa = PBXGroup; children = ( @@ -1619,6 +1642,34 @@ path = Components; sourceTree = ""; }; + 3A70434228E21D9400207D6F /* Completed */ = { + isa = PBXGroup; + children = ( + 3A312FED28B6316D00A290D3 /* CompletedTransactions.swift */, + 3A70433C28E21CFB00207D6F /* CompletedTransaction.swift */, + 3A70433E28E21D3600207D6F /* CompletedTransactionKernel.swift */, + ); + path = Completed; + sourceTree = ""; + }; + 3A70434328E21D9F00207D6F /* Pending Inbound */ = { + isa = PBXGroup; + children = ( + 3AF56BBA28B774C0004E8E43 /* PendingInboundTransactions.swift */, + 3A70434028E21D7A00207D6F /* PendingInboundTransaction.swift */, + ); + path = "Pending Inbound"; + sourceTree = ""; + }; + 3A70434428E21DB900207D6F /* Pending Outbound */ = { + isa = PBXGroup; + children = ( + 3AF56BBC28B77969004E8E43 /* PendingOutboundTransactions.swift */, + 3A70434528E21DD200207D6F /* PendingOutboundTransaction.swift */, + ); + path = "Pending Outbound"; + sourceTree = ""; + }; 3A7097CD27D0E26400641250 /* Mocks */ = { isa = PBXGroup; children = ( @@ -1668,6 +1719,25 @@ path = Views; sourceTree = ""; }; + 3A8473C028EC555B0015E63A /* Services */ = { + isa = PBXGroup; + children = ( + 3A8473C128EC556C0015E63A /* CoreTariService.swift */, + 3A8473C328EC55B10015E63A /* TariTransactionsService.swift */, + 3A8473C528EC55DB0015E63A /* TariContactsService.swift */, + 3A8473C728EC55F50015E63A /* TariValidationService.swift */, + 3A8473C928EC56180015E63A /* TariFeesService.swift */, + 3A8473CB28EC562E0015E63A /* TariConnectionService.swift */, + 3A8473CD28EC56480015E63A /* TariUTXOsService.swift */, + 3A8473CF28EC56600015E63A /* TariRecoveryService.swift */, + 3A8473D128EC568A0015E63A /* TariFaucetService.swift */, + 3A8473D328EC56A90015E63A /* TariEncryptionService.swift */, + 3A8473D528EC56C90015E63A /* TariBalanceService.swift */, + 3A8473D728EC56EF0015E63A /* TariKeyValueService.swift */, + ); + path = Services; + sourceTree = ""; + }; 3A84CDCE2846084C005F2F8D /* UTXOs Wallet */ = { isa = PBXGroup; children = ( @@ -1692,6 +1762,22 @@ path = Home; sourceTree = ""; }; + 3A874036278842E700D80823 /* Core */ = { + isa = PBXGroup; + children = ( + 3AABFC9E28F59B3C00D87773 /* App Setup */, + 3A8473C028EC555B0015E63A /* Services */, + 3AB1114728E60D8E00AA70E8 /* Tor */, + 3AF335DE28D7981E00B48F33 /* Data Models */, + 3A312FEB28B6314A00A290D3 /* FFI */, + 3A47ABCC27478208007F351B /* Tari.swift */, + 3A874037278842F500D80823 /* TorManager.swift */, + 3A8740392788438E00D80823 /* FFIWalletManager.swift */, + 3AC6F11128E9C6590068E6FF /* WalletCallbacksManager.swift */, + ); + path = Core; + sourceTree = ""; + }; 3A87C3B327A9406F007A553F /* Errors */ = { isa = PBXGroup; children = ( @@ -1712,6 +1798,13 @@ path = VerifySeedWords; sourceTree = ""; }; + 3A9D6C2A28E4604D00B14F7C /* Faucet */ = { + isa = PBXGroup; + children = ( + ); + path = Faucet; + sourceTree = ""; + }; 3AA271722790221C0076E51F /* New */ = { isa = PBXGroup; children = ( @@ -1757,12 +1850,27 @@ children = ( 00A66526243C766D0046E730 /* ConnectionMonitor.swift */, 3AA2E2202885755A00D30B62 /* NetworkMonitor.swift */, - 3AA2E2222885759A00D30B62 /* TorMonitor.swift */, - 3AA2E224288576F600D30B62 /* BaseNodeStatusMonitor.swift */, ); path = "Connection Monitor"; sourceTree = ""; }; + 3AABFC9E28F59B3C00D87773 /* App Setup */ = { + isa = PBXGroup; + children = ( + 3A8A03EA28F587FD003EC8FE /* AppConfigurator.swift */, + ); + path = "App Setup"; + sourceTree = ""; + }; + 3AB1114728E60D8E00AA70E8 /* Tor */ = { + isa = PBXGroup; + children = ( + 370E886A24FE9FF800576F61 /* Helpers */, + 370E886224FE974100576F61 /* OnionSettings.swift */, + ); + path = Tor; + sourceTree = ""; + }; 3ABBC5DC2726B38C001BB864 /* Managers */ = { isa = PBXGroup; children = ( @@ -1913,10 +2021,54 @@ path = "Deep Links"; sourceTree = ""; }; + 3AF335DA28D754EC00B48F33 /* Contacts */ = { + isa = PBXGroup; + children = ( + 001F6CDF2380198D00FA7002 /* Contacts.swift */, + 001F6CE32380253B00FA7002 /* Contact.swift */, + ); + path = Contacts; + sourceTree = ""; + }; + 3AF335DB28D789F000B48F33 /* Seed Words */ = { + isa = PBXGroup; + children = ( + 3A74E7DA273BCFF000F290D2 /* SeedWordsMnemonicWordList.swift */, + 00B084C4249E4F6900F7B9BD /* SeedWords.swift */, + ); + path = "Seed Words"; + sourceTree = ""; + }; + 3AF335DC28D78D1F00B48F33 /* Keys */ = { + isa = PBXGroup; + children = ( + 001F6CDB238011CA00FA7002 /* PublicKey.swift */, + 00BBD811237E9EE200EBF5E6 /* PrivateKey.swift */, + ); + path = Keys; + sourceTree = ""; + }; + 3AF335DD28D7905C00B48F33 /* Configs */ = { + isa = PBXGroup; + children = ( + 00BBD819237EB31900EBF5E6 /* CommsConfig.swift */, + 4CD20A352407967B007B64D8 /* TransportConfig.swift */, + ); + path = Configs; + sourceTree = ""; + }; + 3AF335DE28D7981E00B48F33 /* Data Models */ = { + isa = PBXGroup; + children = ( + 3AF335DF28D7983000B48F33 /* MessageMetadata.swift */, + 3A8005CC28EAF9100022A38A /* WalletBalance.swift */, + ); + path = "Data Models"; + sourceTree = ""; + }; 3AF53B7F27311D7100C78562 /* WalletLib */ = { isa = PBXGroup; children = ( - 3AB21735272BEF59007139F1 /* TariLibWrapperTests.swift */, 3AF53B7D27311D1400C78562 /* SeedWordsTests.swift */, ); path = WalletLib; @@ -1962,12 +2114,10 @@ 4C2C95AA2379554B005058AB /* TariLib */ = { isa = PBXGroup; children = ( + 3A874036278842E700D80823 /* Core */, 4C2C95AF23795919005058AB /* libwallet_ffi.a */, 4C2C95B723795930005058AB /* wallet.h */, - 009BF8AB237ABB4700C02E44 /* TariLib.swift */, - 00BCE485240D5A9E00B181F3 /* Tor */, 006D211B23CEDEEE007D1C10 /* Utils */, - 00BBD80C237DA04300EBF5E6 /* Wrappers */, ); path = TariLib; sourceTree = ""; @@ -2303,6 +2453,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A51B1AD28F00A4F0094FDD6 /* Logger.swift in Sources */, 3AE885082837857B0070D1AC /* CurrencyLabelView.swift in Sources */, 3A7E06DC287831D800D15190 /* PopUpCombineUTXOsConfirmationContentView.swift in Sources */, 3A6CD3B5279194980034DF81 /* RequestTariAmountView.swift in Sources */, @@ -2312,20 +2463,19 @@ 3A03683127DF211D00F304A2 /* TransactionDetailsView.swift in Sources */, 3A82455827E1EE38003B6B59 /* TransactionDetailsEmojiView.swift in Sources */, 3AF97E7027CFCA2000FF6A3F /* ShortcutsManager.swift in Sources */, - 001F6CE82381A22E00FA7002 /* CompletedTxs.swift in Sources */, 3ACDA70926B031CB00F138B8 /* RestoreWalletFromSeedsProgressView.swift in Sources */, + 3A87403A2788438E00D80823 /* FFIWalletManager.swift in Sources */, 001F6CE42380253B00FA7002 /* Contact.swift in Sources */, 3AF97E6927CF76C800FF6A3F /* DeeplinkHandler.swift in Sources */, 00325ECA239E4A1000F76918 /* FadedOverlayView.swift in Sources */, 00DD160F241CD41D00C955B4 /* BaseNode.swift in Sources */, 3AF97E6527CF767D00FF6A3F /* TransactionsSendDeeplink.swift in Sources */, 3A35413626A738D4002AB5A8 /* RoundedInputField.swift in Sources */, - 00BCE482240D5A9700B181F3 /* OnionConnector.swift in Sources */, 00D7BD8924349AE3009DD097 /* UIDevice.swift in Sources */, 3AE138D12804A8B300443D34 /* SuccessToast.swift in Sources */, 370E887824FEA54100576F61 /* NetworkTools.m in Sources */, - 004997AF2382E938000A0B7D /* PendingOutboundTx.swift in Sources */, 3A35412C26A72D9F002AB5A8 /* ViewIdentifiable.swift in Sources */, + 3A70433F28E21D3600207D6F /* CompletedTransactionKernel.swift in Sources */, 3A13260127EDE3B900289C8C /* TransactionProgressPresenter.swift in Sources */, 3AA2717C279031950076E51F /* TransactionsToolbarView.swift in Sources */, BFE1FC0023E20E3600BA5EEC /* WalletCreationViewController.swift in Sources */, @@ -2333,9 +2483,9 @@ 3AF51819270D7EFC00746884 /* AddRecipientModel.swift in Sources */, 37ABB69424781F8600F08163 /* UILabelWithPadding.swift in Sources */, 0074619B239A57B000F00966 /* UIBarButtonItem.swift in Sources */, - 009BF8AC237ABB4700C02E44 /* TariLib.swift in Sources */, 3A6A033C2802DD34000432B4 /* PopUpPresenter.swift in Sources */, 3ABBBA8028508AB000A3108D /* UTXOsWalletTextListViewCell.swift in Sources */, + 3A8473CA28EC56180015E63A /* TariFeesService.swift in Sources */, 00E2BCBA236B5BE800C2A105 /* ActionButton.swift in Sources */, 3ABF91AC283FFAA50001C766 /* AboutViewCell.swift in Sources */, 3A80F636287C56D60099EB73 /* PopUpUtxoDetailsContentView.swift in Sources */, @@ -2346,8 +2496,12 @@ 00BBD801237C5B5200EBF5E6 /* CommandLineArgs.swift in Sources */, 3A115B1A27EC4C82001259E5 /* TransactionDetailsBlockExplorerView.swift in Sources */, 37CFAA76248FE28600DEA096 /* NSPointerArray+Extension.swift in Sources */, + 3A8005CD28EAF9110022A38A /* WalletBalance.swift in Sources */, + 3A4D90A4273A75FC00A23FA8 /* Wallet.swift in Sources */, 3A4CE33026A18DFC00ECF460 /* GroupUserDefaults.swift in Sources */, 3A4376EE269C0CC1006107B0 /* RestoreWalletFromSeedsView.swift in Sources */, + 3AF335E028D7983100B48F33 /* MessageMetadata.swift in Sources */, + 3A70434628E21DD200207D6F /* PendingOutboundTransaction.swift in Sources */, 3A82455427E1EDBB003B6B59 /* TransactionDetailsValueView.swift in Sources */, 3708D74A247FCF2300807D72 /* SettingsViewController.swift in Sources */, 4CD20A362407967B007B64D8 /* TransportConfig.swift in Sources */, @@ -2355,29 +2509,35 @@ 00B4D6F6241B778D00ED8318 /* BackgroundTaskManager.swift in Sources */, 375AFDCF2475387700C62CA1 /* WebBrowserViewController.swift in Sources */, 37B4449E24890F6200592D92 /* ICloudBackup.swift in Sources */, + 3A8473D828EC56EF0015E63A /* TariKeyValueService.swift in Sources */, + 3A874038278842F500D80823 /* TorManager.swift in Sources */, 37C8BA47248126B4005BBC05 /* UIScrollView+RefreshControl.swift in Sources */, 3A4949A5283FD603002768AE /* AboutViewController.swift in Sources */, 3A983A6027C67C0500F0AB61 /* VerifySeedWordsConstructor.swift in Sources */, 0012562F2371D81500A9C067 /* SplashViewController.swift in Sources */, - 00A057AF23D875840029C22A /* TariEventBus.swift in Sources */, + 3A70434128E21D7A00207D6F /* PendingInboundTransaction.swift in Sources */, + 3A2F30B428F5940A0095E25D /* ConsoleLogger.swift in Sources */, 3ABF91A9283FFA790001C766 /* AboutModel.swift in Sources */, 3A35413126A72DEE002AB5A8 /* SelectBaseNodeCell.swift in Sources */, + 3A8473D228EC568A0015E63A /* TariFaucetService.swift in Sources */, 37765D2624A35BA20091AE2A /* AESEncryption.swift in Sources */, 3A82455627E1EE0D003B6B59 /* TransactionDetailsSectionView.swift in Sources */, 3AECD494284F46FD00D81C80 /* UIColor+Utils.swift in Sources */, BFD758E623E98ABD00B0F1A5 /* ProfileViewController.swift in Sources */, + 3A8473C428EC55B10015E63A /* TariTransactionsService.swift in Sources */, 3A386F3727A7FC6C0027FED4 /* ErrorMessageManager.swift in Sources */, 3A8A05562853270B00D48FCE /* UIImage+Utils.swift in Sources */, 3AE138C928044A1800443D34 /* PopUpComponentsFactory.swift in Sources */, 4CDEC323273A5E3600999DCB /* Balance.swift in Sources */, 3A4376E5269C0C10006107B0 /* RestoreWalletFromSeedsViewController.swift in Sources */, + 3A8473DA28EC57AA0015E63A /* Publisher+Binding.swift in Sources */, + 3AF56BBD28B77969004E8E43 /* PendingOutboundTransactions.swift in Sources */, 37875E4424D85C4300C0595B /* LoadingGIFButton.swift in Sources */, BFC5532523D9B8E4009130A8 /* UIView+Content.swift in Sources */, 3AA27174279022310076E51F /* TransactionsViewController.swift in Sources */, 3A4205A127980FFA00A8D49C /* QRCodeView.swift in Sources */, 3AC05F9B26C3F302002742C6 /* TransactionsListHeaderView.swift in Sources */, 3AFC6E032745051400A20287 /* SettingsHeaderView.swift in Sources */, - 00BCE489240EB1EE00B181F3 /* SplashViewControllerSetup.swift in Sources */, 3AEDBE5127CE477B006B0166 /* DeepLinkDataEncoder.swift in Sources */, 3AFAEBF226B15A3300B82603 /* KeyboardAvoidingContentView.swift in Sources */, 37B48A7F24B3312000F8A8D2 /* PasswordVerificationViewController.swift in Sources */, @@ -2386,18 +2546,22 @@ 00BBD812237E9EE200EBF5E6 /* PrivateKey.swift in Sources */, 009DFCA72473E33F0061DBC9 /* DeepLinkParams.swift in Sources */, 373CCDBD2490B66E00D0A2C9 /* CircularProgressView.swift in Sources */, - 3AA2E2232885759A00D30B62 /* TorMonitor.swift in Sources */, 3A20A6B82722EE00002B15DB /* VideoView.swift in Sources */, 001F6CE02380198D00FA7002 /* Contacts.swift in Sources */, 0072BF24247E735700BD28FB /* ReminderNotifications.swift in Sources */, + 3A8473C628EC55DB0015E63A /* TariContactsService.swift in Sources */, + 3A2F30B628F594520095E25D /* FileLogger.swift in Sources */, + 3A8473D028EC56600015E63A /* TariRecoveryService.swift in Sources */, + 3AABFC9D28F59B2200D87773 /* StatusLoggerManager.swift in Sources */, 00665E7524E422C500030A13 /* CustomTabBar.swift in Sources */, 3A0EA97E26AA8E3C002612D4 /* TokenView.swift in Sources */, 3A74E7DB273BCFF000F290D2 /* SeedWordsMnemonicWordList.swift in Sources */, + 3A47ABCD27478208007F351B /* Tari.swift in Sources */, + 3A8A03EB28F587FD003EC8FE /* AppConfigurator.swift in Sources */, 00BBD80E237DA07400EBF5E6 /* ByteVector.swift in Sources */, 3A39AF1C27B1570F00A32F46 /* SettingsViewFooter.swift in Sources */, 3ACDA85C27215BEB00F08C70 /* YatTransactionView.swift in Sources */, 3A03682E27DF20E300F304A2 /* TransactionDetailsViewController.swift in Sources */, - 3AA2E225288576F600D30B62 /* BaseNodeStatusMonitor.swift in Sources */, 3A2C0B8927DA22C20018C5A8 /* ExpandButton.swift in Sources */, 3AE1D58027E0A894001A46C4 /* TransactionDetailsModel.swift in Sources */, 00BBD81A237EB31900EBF5E6 /* CommsConfig.swift in Sources */, @@ -2407,13 +2571,13 @@ 00E491A62366E08B007B332D /* MobileWallet.xcdatamodeld in Sources */, 3ABF91A7283FFA6E0001C766 /* AboutView.swift in Sources */, 00230AFA2376C6DE00F23E34 /* Sequence.swift in Sources */, + 3A8005C828EAF1AB0022A38A /* RestoreWalletStatus.swift in Sources */, 37CB9F8A2451A2AD00C495F2 /* String+Emoji.swift in Sources */, + 3AC6F11228E9C6590068E6FF /* WalletCallbacksManager.swift in Sources */, 3AEDBE4D27CE45C0006B0166 /* DeepLinkFormatter.swift in Sources */, 3ACDA70E26B0320900F138B8 /* RestoreWalletFromSeedsProgressModel.swift in Sources */, 3AE138CF2804A88500443D34 /* ToastPresenter.swift in Sources */, - 009DFCB124741F9B0061DBC9 /* WalletTxs.swift in Sources */, 3A0EA98226AA8E64002612D4 /* TokenInputView.swift in Sources */, - 00369D812426080D00BAB95B /* TariLogger.swift in Sources */, 3ADEBF6226A59A5900E87C84 /* AddBaseNodeViewController.swift in Sources */, 3A4BF7CE27B5712900CA499D /* Result+Void.swift in Sources */, 3A78860E2872FB39003B1F9A /* TariVectorWrapper.swift in Sources */, @@ -2423,17 +2587,15 @@ 001F6CDC238011CA00FA7002 /* PublicKey.swift in Sources */, 3A860BD2287DA2B800FC4F76 /* HomeViewModel.swift in Sources */, 00F53A642428E44700448466 /* StorageCleanup.swift in Sources */, - 004997B32382ED68000A0B7D /* PendingInboundTx.swift in Sources */, 370E886324FE974100576F61 /* OnionSettings.swift in Sources */, 0087A1B223F4235400B89EE7 /* AddRecipientViewController.swift in Sources */, 378EE9A3250126BE009615B5 /* CustomBridgesViewController.swift in Sources */, - 0091D4A723ED879E004BF7F7 /* Signature.swift in Sources */, 0053873024065F6C00901A68 /* SlideView.swift in Sources */, - 00BBD816237EA67600EBF5E6 /* Wallet.swift in Sources */, 3AE138CB28044B1E00443D34 /* CustomDeeplinkPopUpContentView.swift in Sources */, - 3A4D90A2273A728300A23FA8 /* WalletConnectivityManager.swift in Sources */, + 3A2F30B828F5946B0095E25D /* CrashLogger.swift in Sources */, 3A66109927AD364E0038EB5B /* SendingTariModel.swift in Sources */, 00A66527243C766D0046E730 /* ConnectionMonitor.swift in Sources */, + 3AC6F11428E9D8840068E6FF /* CustomBridgesHandable.swift in Sources */, 00E4919A2366E08B007B332D /* AppDelegate.swift in Sources */, 3A62674626D76E6B007F9895 /* SelectNetworkViewController.swift in Sources */, 00B084C0249E471800F7B9BD /* EmojiSet.swift in Sources */, @@ -2448,12 +2610,15 @@ 3723A7A624AC9389003382EB /* SecureBackupViewController.swift in Sources */, 37875E4824D8787A00C0595B /* TxTableViewModel.swift in Sources */, 3AF97E6727CF769A00FF6A3F /* BaseNodesAddDeeplink.swift in Sources */, + 3A8005CF28EAF9380022A38A /* BaseNodeConnectivityStatus.swift in Sources */, 3AE138D32804AB4100443D34 /* UIApplication+Keyboard.swift in Sources */, 3A115B1827EC4B14001259E5 /* TransactionDetailsSeparatorView.swift in Sources */, + 3A8473CC28EC562E0015E63A /* TariConnectionService.swift in Sources */, 3A84CDD62846089B005F2F8D /* UTXOsWalletModel.swift in Sources */, 3AA271772790247E0076E51F /* TransactionsView.swift in Sources */, 004277FF23E2F1D700AE7BD9 /* UIImage.swift in Sources */, 37C8BA4F24813F98005BBC05 /* SettingsParentTableViewController.swift in Sources */, + 3A8473C828EC55F50015E63A /* TariValidationService.swift in Sources */, 0091D4AB23ED87BE004BF7F7 /* KeyServer.swift in Sources */, 3AE8850C28378BF20070D1AC /* TariSegmentedControl.swift in Sources */, 37E4B62B24501FB200FA302B /* HomeViewController.swift in Sources */, @@ -2463,14 +2628,15 @@ 3A494A422716D6DA00CF5B05 /* AddRecipientView.swift in Sources */, 00E4919C2366E08B007B332D /* SceneDelegate.swift in Sources */, 37049270247EA0770034EE5D /* RestoreWalletViewController.swift in Sources */, - 00947CC723D1A35200526DD5 /* TxsProtocol.swift in Sources */, 3ABBC5E02726D104001BB864 /* YatTransactionConstructor.swift in Sources */, 3A87E57F28409D630099EA0E /* AddAmountSpinnerView.swift in Sources */, 3A01248327B3BD0700A481F4 /* ProgressBar.swift in Sources */, 3AA2DD942796E8BA00DC3CF7 /* QRCodePresentationController.swift in Sources */, + 3A8473CE28EC56480015E63A /* TariUTXOsService.swift in Sources */, 3A4CE32C26A18D7300ECF460 /* UserDefault.swift in Sources */, 3A1D8E4B28400AB8004129D5 /* AboutViewHeader.swift in Sources */, 3AD1306827D6290A003EC7FA /* SeedWordsListViewController.swift in Sources */, + 3AF56BBB28B774C0004E8E43 /* PendingInboundTransactions.swift in Sources */, A0779C612552C1AF00614EF3 /* DeleteWalletViewController.swift in Sources */, 37EE285A2485649800335EDC /* WordsFlexView.swift in Sources */, 3ADA05F127A41D44007F5677 /* PointerHandler.swift in Sources */, @@ -2488,6 +2654,7 @@ 37D3D4ED251C970F00D24149 /* TxGifManager.swift in Sources */, 3A62674A26D76EC4007F9895 /* SelectNetworkModel.swift in Sources */, 3A35413E26A73939002AB5A8 /* RoundedTextView.swift in Sources */, + 3A8473D428EC56A90015E63A /* TariEncryptionService.swift in Sources */, 3ACDA85A2721578700F08C70 /* YatTransactionViewController.swift in Sources */, 00C185B9238943980096984E /* UIViewControllerDebugExtension.swift in Sources */, 3AC877E628741680006F327B /* UTXOsWalletLoadingView.swift in Sources */, @@ -2497,21 +2664,21 @@ 3AE5E56A287469E500D3AF85 /* TextCapsule.swift in Sources */, 3A6A033E2802DEEB000432B4 /* PopUpPresenter+CommonPopUps.swift in Sources */, 3ADEBF5D26A54FA500E87C84 /* SelectBaseNodeModel.swift in Sources */, + 3A8005CB28EAF3A90022A38A /* TransactionValidationData.swift in Sources */, 005387242405136700901A68 /* AddNoteViewController.swift in Sources */, 3AE9F4E42795A260006101D1 /* RequestTariAmountModel.swift in Sources */, 3AA2E2212885755A00D30B62 /* NetworkMonitor.swift in Sources */, - 00BCE47F240D5A9700B181F3 /* OnionManager.swift in Sources */, + 3A03A600288932DE00788A02 /* SplashView.swift in Sources */, 3A052BFA27B2940900C93671 /* WalletTransactionsManager.swift in Sources */, - 00A9190B242CDD7700B7F14D /* Tracker.swift in Sources */, 3AE8850A28378B8C0070D1AC /* NetworkTrafficView.swift in Sources */, 00262EB4236F024400A6C8A0 /* Theme.swift in Sources */, 3A93159B286C30AA0001660E /* LeftImageButton.swift in Sources */, 3AC8C25D27999CAC00334F26 /* PageViewController.swift in Sources */, 00B084C5249E4F6900F7B9BD /* SeedWords.swift in Sources */, - 004997B72382ED79000A0B7D /* PendingInboundTxs.swift in Sources */, 00FB43D824BC937D0031D53E /* BackupPrompts.swift in Sources */, 37E0B008249B700F00DFE315 /* UIFont+FontStyle.swift in Sources */, 0039B1B223FA925500F91E0F /* PasteEmojisView.swift in Sources */, + 3A8473C228EC556C0015E63A /* CoreTariService.swift in Sources */, 3A2B956A28648D860085BE21 /* CChar+Helpers.swift in Sources */, 3A0EA97A26AA8E1C002612D4 /* TokenCollectionView.swift in Sources */, 37049274247EA9110034EE5D /* SystemMenuTableViewCell.swift in Sources */, @@ -2519,11 +2686,10 @@ 00D988692449B04B000FDC9C /* AnimatedRefreshingView.swift in Sources */, 3A237C7D27D8E141005FA6AB /* SeedWordsListConstructor.swift in Sources */, 3A84CDD828460967005F2F8D /* UTXOsWalletConstructor.swift in Sources */, - 00947CC223CF090D00526DD5 /* TxProtocol.swift in Sources */, 3ACFECAA27ABD0B100FA9FE1 /* SendingTariProgressBar.swift in Sources */, 00BBD809237D80C800EBF5E6 /* Date.swift in Sources */, + 3AFC3D60288EC3FA0046D386 /* UnselectableTextView.swift in Sources */, 3A7A38D32881689A00D7E8BD /* PopUpNetworkStatusContentView.swift in Sources */, - 3AF97E6C27CF988D00FF6A3F /* BaseNodeManager.swift in Sources */, 3AF79D602727206200613C24 /* AddRecipientSearchView.swift in Sources */, 3AECD490284F3BB300D81C80 /* BaseNavigationContentView.swift in Sources */, 37547D522460165500EB59CC /* UIApplication+KeyWindow.swift in Sources */, @@ -2532,6 +2698,7 @@ 3A237C7B27D8D0F7005FA6AB /* SeedWordsListModel.swift in Sources */, 3A84CDD42846088D005F2F8D /* UTXOsWalletView.swift in Sources */, 3A2C0B8727DA22970018C5A8 /* SeedWordListElementView.swift in Sources */, + 3A8473D628EC56C90015E63A /* TariBalanceService.swift in Sources */, 3A6F3FF8283BF980005D1793 /* TariFeePerGramStats.swift in Sources */, 3A94DB21283E7CEF00B0A740 /* TransactionFeesManager.swift in Sources */, 3ACC4F602876D2F100632F64 /* NSAttributedString+Format.swift in Sources */, @@ -2541,19 +2708,19 @@ 00A66523243B3E380046E730 /* ErrorView.swift in Sources */, 3ADEBF6626A5B5A500E87C84 /* AddBaseNodeView.swift in Sources */, 3AF985FA286B233100290387 /* PopUpSelectionView.swift in Sources */, - 4CF0911B26E17CEE00582972 /* CompletedTxKernel.swift in Sources */, 375DB1E0246E90D100B2BEF4 /* NavigationBar.swift in Sources */, 3708D752247FF7D000807D72 /* BackupWalletSettingsViewController.swift in Sources */, + 3A70433D28E21CFB00207D6F /* CompletedTransaction.swift in Sources */, 3A72DC4526DFA26000E0BC43 /* TariNetwork.swift in Sources */, 004277F723E0628A00AE7BD9 /* UIViewController+Content.swift in Sources */, 3A2B95682864801D0085BE21 /* UTXOsWalletTileListLayout.swift in Sources */, 373CCDC52490F3B600D0A2C9 /* FileManager+SecureCopy.swift in Sources */, 3ACFA429278EC9BF00EBED98 /* ProfileView.swift in Sources */, - 004997AB2382E893000A0B7D /* PendingOutboundTxs.swift in Sources */, 0087A1AF23F4235400B89EE7 /* ContactCell.swift in Sources */, 370E887224FEA32600576F61 /* Ipv6Tester.swift in Sources */, 49996E1923F164BA002B6696 /* AnimatedBalanceLabel.swift in Sources */, 3AEE7AA3286599E6000F84ED /* UTXOsWalletTopBar.swift in Sources */, + 3AABFC9B28F5960100D87773 /* LogFormatter.swift in Sources */, 491AB8A023F3FF4400372189 /* AddAmountViewController.swift in Sources */, 3A42059A279804B300A8D49C /* QRCodeFactory.swift in Sources */, 3ACFDD1D26E8C1A900C5E1EA /* AppInfo.swift in Sources */, @@ -2567,9 +2734,12 @@ 3A82455A27E1EE93003B6B59 /* TransactionDetailsContactView.swift in Sources */, 3A4CE33626A1A04000ECF460 /* SelectBaseNodeViewController.swift in Sources */, 3AA9499E26BD3EAC007C550D /* HomeViewToolbar.swift in Sources */, + 3A70434828E21F5600207D6F /* Transaction.swift in Sources */, + 3A8473BF28EC51780015E63A /* TxoValidationStatus.swift in Sources */, 37B444A9248949B800592D92 /* Checkbox.swift in Sources */, BFF1ED9D2408111000CC9EF6 /* SendingTariViewController.swift in Sources */, 3AC877E4287415AD006F327B /* UTXOsWalletPlaceholderView.swift in Sources */, + 3A8005C628EAF0A50022A38A /* TransactionSendResult.swift in Sources */, 3776080924B73500008167E8 /* Backup.swift in Sources */, 3A84CDD22846087D005F2F8D /* UTXOsWalletViewController.swift in Sources */, 3A42059D279804F300A8D49C /* AmountNumberFormatter.swift in Sources */, @@ -2577,11 +2747,12 @@ 3AE138D52804ACE100443D34 /* WebBrowserPresenter.swift in Sources */, 37B5289424CB1E16008C80EB /* NetworkSpeedProvider.swift in Sources */, 3A8A0558285370B200D48FCE /* UTXOsWalletTextTickButton.swift in Sources */, - 001F6CEC2381A39300FA7002 /* CompletedTx.swift in Sources */, 3730E15124C884D500827DFA /* MenuTabBarController.swift in Sources */, 3A6A033A2802DCE7000432B4 /* TariPopUp.swift in Sources */, 371A0818247290C000F97713 /* TransitionLabel.swift in Sources */, 3A4205922798001A00A8D49C /* AmountKeyboardView.swift in Sources */, + 3A03A602288962FA00788A02 /* SplashViewModel.swift in Sources */, + 3A312FEE28B6316D00A290D3 /* CompletedTransactions.swift in Sources */, 00E2BCB2236B455900C2A105 /* HomeViewFloatingPanelDelegates.swift in Sources */, 3ADF96CC28585E2400A3C888 /* ContextualButtonsOverlay.swift in Sources */, 37ABB69024781CE800F08163 /* UILabel.swift in Sources */, @@ -2602,9 +2773,7 @@ 3A13260327EDE42A00289C8C /* GradientView.swift in Sources */, 37BB696B245705D20013AC4D /* RadialGradientView.swift in Sources */, 3A7E06DA287831BD00D15190 /* UTXOsEstimationLabel.swift in Sources */, - 004277FB23E1A61F00AE7BD9 /* ErrorDescriptions.swift in Sources */, 0042780323E8421200AE7BD9 /* String.swift in Sources */, - 3AF167442782FD7C0019A30E /* Wallet+Conveniences.swift in Sources */, 6191948FA1B3F223B37A0D9B /* UTXO.swift in Sources */, 3A793BF3280354830094DF23 /* PopUpHeaderWithSubtitle.swift in Sources */, 3A3E7AD6284E03140065F3C0 /* UTXOTileView.swift in Sources */, @@ -2628,7 +2797,6 @@ 3AF53B7E27311D1400C78562 /* SeedWordsTests.swift in Sources */, 3A87C3B927A96423007A553F /* ErrorMessageManagerTests.swift in Sources */, 3AEDBE5927CE4F80006B0166 /* DeepLinkFormatterTests.swift in Sources */, - 3AB21736272BEF5A007139F1 /* TariLibWrapperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2806,7 +2974,7 @@ "$(inherited)", "$(PROJECT_DIR)/MobileWallet/TariLib", ); - MARKETING_VERSION = 0.15.1; + MARKETING_VERSION = 0.16.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2842,7 +3010,7 @@ "$(inherited)", "$(PROJECT_DIR)/MobileWallet/TariLib", ); - MARKETING_VERSION = 0.15.1; + MARKETING_VERSION = 0.16.0; PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet; PRODUCT_NAME = "Tari Aurora"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2858,8 +3026,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8XGMD9X2H2; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2883,8 +3053,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8XGMD9X2H2; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme index 293b5288..a8695620 100644 --- a/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme +++ b/MobileWallet.xcodeproj/xcshareddata/xcschemes/MobileWallet.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -47,6 +47,10 @@ BlueprintName = "UnitTests" ReferencedContainer = "container:MobileWallet.xcodeproj"> + + diff --git a/MobileWallet/AppDelegate.swift b/MobileWallet/AppDelegate.swift index e3694d23..8a28b470 100644 --- a/MobileWallet/AppDelegate.swift +++ b/MobileWallet/AppDelegate.swift @@ -41,12 +41,13 @@ import UIKit import CoreData import AVFoundation -import Sentry import GiphyUISDK import GiphyCoreSDK @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { + + private let statusLoggerManager = StatusLoggerManager() func application( _ application: UIApplication, @@ -54,7 +55,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ) -> Bool { // Override point for customization after application launch. handleCommandLineArgs() - setupSentryCrashReporting() UNUserNotificationCenter.current().delegate = self try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .mixWithOthers) @@ -64,43 +64,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD if let giphyApiKey = TariSettings.shared.giphyApiKey { Giphy.configure(apiKey: giphyApiKey) } - + + AppConfigurator.configureLoggers() + return true } - private func setupSentryCrashReporting() { - guard TariSettings.shared.environment != .debug, - let sentryPublicDSN = TariSettings.shared.sentryPublicDSN else { - return - } - SentrySDK.start(options: [ - "dsn": sentryPublicDSN, - "debug": false - ]) - TariLogger.info("Sentry crash reporting has been started.") - - TariLogger.breadcrumbCallback = { (message, loggerLevel) in - var sentryLevel: SentryLevel = .debug - switch loggerLevel { - case .error: - sentryLevel = .error - case .info: - sentryLevel = .info - case .warning: - sentryLevel = .warning - default: - sentryLevel = .debug - } - - let crumb = Breadcrumb( - level: sentryLevel, - category: "TariLogger \(loggerLevel.rawValue)" - ) - crumb.message = message - return SentrySDK.addBreadcrumb(crumb: crumb) - } - } - func applicationWillTerminate(_ application: UIApplication) { if ICloudBackup.shared.inProgress || BackupScheduler.shared.isBackupScheduled { UserDefaults.Key.backupOperationAborted.set(true) @@ -137,7 +106,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { let hexString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() - TariLogger.info("Registed for push notifications with token \(hexString).") + Logger.log(message: "Registed for push notifications with token \(hexString).", domain: .general, level: .info) NotificationManager.shared.registerDeviceToken(deviceToken) } @@ -145,7 +114,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error ) { - TariLogger.error("Failed to register for push notifications", error: error) + Logger.log(message: "Failed to register for push notifications: \(error.localizedDescription)", domain: .general, level: .error) } func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool { diff --git a/MobileWallet/Assets.xcassets/Colors/HomeScreenBackground.colorset/Contents.json b/MobileWallet/Assets.xcassets/Colors/HomeScreenBackground.colorset/Contents.json deleted file mode 100644 index df10105c..00000000 --- a/MobileWallet/Assets.xcassets/Colors/HomeScreenBackground.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0.538", - "alpha" : "1.000", - "blue" : "1.000", - "green" : "0.191" - } - } - } - ] -} \ No newline at end of file diff --git a/MobileWallet/Assets.xcassets/Colors/SplashSubtitle.colorset/Contents.json b/MobileWallet/Assets.xcassets/Colors/SplashSubtitle.colorset/Contents.json deleted file mode 100644 index a0b6e5bb..00000000 --- a/MobileWallet/Assets.xcassets/Colors/SplashSubtitle.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "1.000", - "alpha" : "0.800", - "blue" : "1.000", - "green" : "1.000" - } - } - } - ] -} \ No newline at end of file diff --git a/MobileWallet/Assets.xcassets/Colors/SplashTitle.colorset/Contents.json b/MobileWallet/Assets.xcassets/Colors/SplashTitle.colorset/Contents.json deleted file mode 100644 index c6e5d3d4..00000000 --- a/MobileWallet/Assets.xcassets/Colors/SplashTitle.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "1.000", - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000" - } - } - } - ] -} \ No newline at end of file diff --git a/MobileWallet/Assets.xcassets/Colors/SplashVersionLabel.colorset/Contents.json b/MobileWallet/Assets.xcassets/Colors/SplashVersionLabel.colorset/Contents.json deleted file mode 100644 index 3eec02e4..00000000 --- a/MobileWallet/Assets.xcassets/Colors/SplashVersionLabel.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "201", - "alpha" : "0.500", - "blue" : "201", - "green" : "201" - } - } - } - ] -} \ No newline at end of file diff --git a/MobileWallet/Backup/BackupPrompts.swift b/MobileWallet/Backup/BackupPrompts.swift index f4db76ab..94e7731d 100644 --- a/MobileWallet/Backup/BackupPrompts.swift +++ b/MobileWallet/Backup/BackupPrompts.swift @@ -134,29 +134,19 @@ private class BackupPrompts { private init () {} func check(_ vc: UIViewController) { - guard let wallet = TariLib.shared.tariWallet else { - return - } for type in PromptType.allCases.reversed() { // If they have been shown this once, skip over this prompt guard UserDefaults.standard.bool(forKey: type.userDefaultsKey) == false else { continue } - - var incomingTxs = wallet.pendingInboundTxs.0?.count.0 ?? 0 - let completedTxs: [CompletedTx] = (wallet.completedTxs.0?.list.0 ?? []) - completedTxs.forEach { (tx) in - if tx.direction == .inbound { - incomingTxs += 1 - } - } - - let balance = (try? wallet.totalBalance) ?? MicroTari() + + let incomingTransactionsCount = Tari.shared.transactions.pendingInbound.count + Tari.shared.transactions.completed.filter { (try? $0.isOutboundTransaction) == false }.count + let balance = Tari.shared.walletBalance.balance.total let triggers = type.triggers - guard incomingTxs >= triggers.numberOfIncomingTxs && - balance.rawValue >= triggers.totalBalance.rawValue && + guard incomingTransactionsCount >= triggers.numberOfIncomingTxs && + balance >= triggers.totalBalance.rawValue && ICloudBackup.shared.isValidBackupExists() == triggers.hasConnectediCloud && (ICloudBackup.shared.lastBackup?.isEncrypted ?? false) == triggers.backupIsEncrypted && !ICloudBackup.shared.iCloudBackupsIsOn diff --git a/MobileWallet/Backup/BackupScheduler.swift b/MobileWallet/Backup/BackupScheduler.swift index 32593cf1..88333cb8 100644 --- a/MobileWallet/Backup/BackupScheduler.swift +++ b/MobileWallet/Backup/BackupScheduler.swift @@ -38,7 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation +import Combine class BackupScheduler: NSObject { private let autoBackupTimeInterval: TimeInterval = 60.0 // 1 min @@ -66,6 +66,7 @@ class BackupScheduler: NSObject { static let shared = BackupScheduler() private var timer = Timer() private(set) var scheduledBackupStarted: Bool = false + private var cancellables = Set() override init() { super.init() @@ -73,15 +74,17 @@ class BackupScheduler: NSObject { } func startObserveEvents() { - TariEventBus.onMainThread(self, eventType: .requiresBackup) { - [weak self] (_) in - self?.scheduleBackup() - } + + cancellables.forEach { $0.cancel() } + + Tari.shared.transactions.onUpdate + .sink { [weak self] in self?.scheduleBackup() } + .store(in: &cancellables) } func stopObserveEvents() { timer.invalidate() - TariEventBus.unregister(self) + cancellables.forEach { $0.cancel() } } func scheduleBackup(immediately: Bool = false) { @@ -99,22 +102,6 @@ class BackupScheduler: NSObject { } private func createWalletBackup() { - guard TariLib.shared.walletState == .started else { - TariEventBus.onMainThread(self, eventType: .walletStateChanged) { - [weak self] - (sender) in - guard let self = self else { return } - let walletState = sender!.object as! TariLib.WalletState - switch walletState { - case .started: - TariEventBus.unregister(self, eventType: .walletStateChanged) - self.createWalletBackup() - default: - break - } - } - return - } scheduledBackupStarted = true do { let password = AppKeychainWrapper.loadBackupPasswordFromKeychain() @@ -147,9 +134,8 @@ extension BackupScheduler: ICloudBackupObserver { identifier: NotificationManager.NotificationIdentifier.scheduledBackupFailure.rawValue ) { (_) in - TariLogger.info("Scheduled backup has failed.") + Logger.log(message: "Scheduled backup has failed.", domain: .general, level: .info) self.scheduledBackupStarted = false } } - } diff --git a/MobileWallet/Backup/ICloudBackup.swift b/MobileWallet/Backup/ICloudBackup.swift index 7e4397b3..a997b1cf 100644 --- a/MobileWallet/Backup/ICloudBackup.swift +++ b/MobileWallet/Backup/ICloudBackup.swift @@ -125,7 +125,8 @@ class ICloudBackup: NSObject { private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier? - private var directory: URL { TariLib.shared.connectedDatabaseDirectory } + private var connectedDatabaseDirectory = Tari.shared.connectedDatabaseDirectory + private var walletPublicKeyHex: String? { try? Tari.shared.walletPublicKey.byteVector.hex } private let fileName = "Tari-Aurora-Backup" private var observers = NSPointerArray.weakObjects() @@ -255,17 +256,17 @@ class ICloudBackup: NSObject { } func restoreWallet(password: String?, completion: @escaping (_ error: Error?) -> Void) { - let dbDirectory = TariLib.shared.connectedDatabaseDirectory do { try FileManager.default.createDirectory( - at: dbDirectory, + at: connectedDatabaseDirectory, withIntermediateDirectories: true, attributes: nil ) - restoreBackup(password: password, to: dbDirectory) { [weak self] error in + restoreBackup(password: password, to: connectedDatabaseDirectory) { [weak self] error in + guard let self = self else { return } if error == nil { - TariSettings.shared.walletSettings.configationState = .authorized - self?.iCloudBackupsIsOn = true + TariSettings.shared.walletSettings.configurationState = .authorized + self.iCloudBackupsIsOn = true BackupScheduler.shared.startObserveEvents() @@ -274,7 +275,7 @@ class ICloudBackup: NSObject { } } else { do { - try FileManager.default.removeItem(at: dbDirectory) + try FileManager.default.removeItem(at: self.connectedDatabaseDirectory) } catch { completion(error) return @@ -288,11 +289,11 @@ class ICloudBackup: NSObject { } func removeCurrentWalletBackup() { - if TariLib.shared.walletPublicKeyHex != nil { + if walletPublicKeyHex != nil { do { try FileManager.default.removeBackup(getLastWalletBackup()) } catch { - TariLogger.error("Failed to remove wallet backup", error: error) + Logger.log(message: "Failed to remove wallet backup: \(error.localizedDescription)", domain: .general, level: .error) } } } @@ -478,7 +479,7 @@ extension ICloudBackup { return } - TariLogger.info("Starting iCloud backup in the background") + Logger.log(message: "Starting iCloud backup in the background", domain: .general, level: .info) // Perform the task on a background queue. DispatchQueue.global().async { [weak self] in @@ -493,7 +494,7 @@ extension ICloudBackup { do { try self.createWalletBackup(password: password) } catch { - TariLogger.error("Failed to create wallet backup", error: error) + Logger.log(message: "Failed to create wallet backup: \(error.localizedDescription)", domain: .general, level: .error) self.endBackgroundBackupTask() } } @@ -514,7 +515,7 @@ extension ICloudBackup { body: body, identifier: NotificationManager.NotificationIdentifier.backgroundBackupTask.rawValue ) { (_) in - TariLogger.info("User reminded to open the app as a background backup did not complete.") + Logger.log(message: "User reminded to open the app as a background backup did not complete.", domain: .general, level: .info) self.endBackgroundBackupTask() } } @@ -676,7 +677,7 @@ extension ICloudBackup { let iCloudFolderURL = try iCloudDirectory().appendingPathComponent(NetworkManager.shared.selectedNetwork.name) do { // if walletKeyHex != nil we find last backup for current wallet - if let walletKeyHex = TariLib.shared.walletPublicKeyHex { + if let walletKeyHex = walletPublicKeyHex { if let lastWalletFolder = try FileManager.default.contentsOfDirectory( atURL: iCloudFolderURL, sortedBy: .created, @@ -715,8 +716,8 @@ extension ICloudBackup { private func zipWalletDatabase() throws -> URL { - defer { try? TariLib.shared.tariWallet?.enableEncryption() } - try TariLib.shared.tariWallet?.disableEncryption() + defer { try? Tari.shared.encryption.apply() } + try Tari.shared.encryption.remove() let archiveName = fileName + ".zip" let tmpDirectory = try getTempDirectory() @@ -726,8 +727,8 @@ extension ICloudBackup { try FileManager.default.removeItem(atPath: archiveURL.path) } - let sqlite3File = TariLib.shared.connectedDatabaseName.appending(".sqlite3") - let originalFileURL = directory.appendingPathComponent(sqlite3File) + let sqlite3File = Tari.shared.databaseName.appending(".sqlite3") + let originalFileURL = connectedDatabaseDirectory.appendingPathComponent(sqlite3File) let dbDirectory = TariSettings.shared.isUnitTesting ? try getTestDataBaseUrl() : originalFileURL try FileManager().zipItem( @@ -773,7 +774,7 @@ extension ICloudBackup { } private func remoteDirectoryPath() throws -> String { - guard let publicKey = TariLib.shared.walletPublicKeyHex else { throw ICloudBackupError.unableCreateBackupFolder } + guard let publicKey = walletPublicKeyHex else { throw ICloudBackupError.unableCreateBackupFolder } return "\(NetworkManager.shared.selectedNetwork.name)/\(publicKey)" } diff --git a/MobileWallet/Common/AppRouter.swift b/MobileWallet/Common/AppRouter.swift index 172e5de7..4431fb33 100644 --- a/MobileWallet/Common/AppRouter.swift +++ b/MobileWallet/Common/AppRouter.swift @@ -42,17 +42,78 @@ import UIKit enum AppRouter { + private enum TransitionType { + case moveDown + case crossDissolve + case none + } + static var isNavigationReady: Bool { tabBar != nil } private static var tabBar: MenuTabBarController? { UIApplication.shared.menuTabBarController } - - static func moveToSplashScreen() { + + // MARK: - Transitions + + static func transitionToSplashScreen(animated: Bool = true) { + BackupScheduler.shared.stopObserveEvents() + let navigationController = AlwaysPoppableNavigationController(rootViewController: SplashViewController()) navigationController.setNavigationBarHidden(true, animated: false) - UIApplication.shared.windows.first?.rootViewController = navigationController - UIApplication.shared.windows.first?.makeKeyAndVisible() + + transition(to: navigationController, type: animated ? .moveDown : .none) + } + + static func transitionToOnboardingScreen(startFromLocalAuth: Bool) { + + let controller = WalletCreationViewController() + controller.startFromLocalAuth = startFromLocalAuth + + transition(to: controller, type: .moveDown) } + static func transitionToHomeScreen() { + + let tabBarController = MenuTabBarController() + let navigationController = AlwaysPoppableNavigationController(rootViewController: tabBarController) + + transition(to: navigationController, type: .crossDissolve) + } + + private static func transition(to controller: UIViewController, type: TransitionType) { + + guard let window = UIApplication.shared.windows.first else { return } + + guard type != .none else { + window.rootViewController = controller + return + } + + let snapshot = UIScreen.main.snapshotView(afterScreenUpdates: false) + controller.view.addSubview(snapshot) + + window.rootViewController = controller + + UIView.animate( + withDuration: 0.4, + animations: { update(snapshot: snapshot, controller: controller, transitionType: type) }, + completion: { _ in snapshot.removeFromSuperview() } + ) + } + + private static func update(snapshot: UIView, controller: UIViewController, transitionType: TransitionType) { + + switch transitionType { + case .moveDown: + snapshot.frame.origin.y = controller.view.bounds.maxY + case .crossDissolve: + snapshot.alpha = 0.0 + case .none: + return + } + } + + // MARK: - TabBar Actions + static func moveToTransactionSend(deeplink: TransactionsSendDeeplink?) { tabBar?.homeViewController.onSend(deeplink: deeplink) } diff --git a/MobileWallet/Common/BackgroundTasks/BackgroundTaskManager.swift b/MobileWallet/Common/BackgroundTasks/BackgroundTaskManager.swift index ac8ecabc..ffb56554 100644 --- a/MobileWallet/Common/BackgroundTasks/BackgroundTaskManager.swift +++ b/MobileWallet/Common/BackgroundTasks/BackgroundTaskManager.swift @@ -70,7 +70,7 @@ struct BackgroundTaskManager { do { try BGTaskScheduler.shared.submit(taskRequest) } catch { - TariLogger.error("Scheduling task to schedule reminder notifications", error: error) + Logger.log(message: "Scheduling task to schedule reminder notifications: \(error.localizedDescription)", domain: .general, level: .error) } } diff --git a/MobileWallet/Common/BackgroundTasks/ScheduleReminderNotificationsOperation.swift b/MobileWallet/Common/BackgroundTasks/ScheduleReminderNotificationsOperation.swift index 9a698500..8eb7df1f 100644 --- a/MobileWallet/Common/BackgroundTasks/ScheduleReminderNotificationsOperation.swift +++ b/MobileWallet/Common/BackgroundTasks/ScheduleReminderNotificationsOperation.swift @@ -49,7 +49,7 @@ class ScheduleReminderNotificationsOperation: Operation { } guard let setAt = ReminderNotifications.shared.shouldScheduleRemindersUpdatedAt else { - TariLogger.warn("Nothing to schedule") + Logger.log(message: "Nothing to schedule", domain: .general, level: .warning) onComplete(true) return } @@ -57,7 +57,7 @@ class ScheduleReminderNotificationsOperation: Operation { NotificationManager.shared.cancelAllFutureReminderNotifications() guard let firstReminder = ReminderNotifications.recipientReminderNotifications.first else { - TariLogger.warn("No recipient reminder notifications setup") + Logger.log(message: "No recipient reminder notifications setup", domain: .general, level: .warning) return } @@ -65,7 +65,7 @@ class ScheduleReminderNotificationsOperation: Operation { let intervalOffset = Date().timeIntervalSince(setAt) guard intervalOffset < firstReminder.deliverAfter else { - TariLogger.warn("Background refresh did not run in time. Too late to schedule reminders.") + Logger.log(message: "Background refresh did not run in time. Too late to schedule reminders.", domain: .general, level: .warning) return } @@ -84,7 +84,7 @@ class ScheduleReminderNotificationsOperation: Operation { } } - TariLogger.info("Reminders scheduled") + Logger.log(message: "Reminders scheduled", domain: .general, level: .info) } private func onComplete(_ success: Bool) { diff --git a/MobileWallet/Common/Deep Links/DeeplinkHandler.swift b/MobileWallet/Common/Deep Links/DeeplinkHandler.swift index 684f3bba..f62213da 100644 --- a/MobileWallet/Common/Deep Links/DeeplinkHandler.swift +++ b/MobileWallet/Common/Deep Links/DeeplinkHandler.swift @@ -144,7 +144,7 @@ enum DeeplinkHandler { contentSection.update(name: name, peer: peer) let buttonSection = PopUpComponentsFactory.makeButtonsView(models: [ - PopUpDialogButtonModel(title: localized("add_base_node_overlay.button.confirm"), type: .normal, callback: { try? BaseNodeManager.addBaseNode(name: name, peer: peer) }), + PopUpDialogButtonModel(title: localized("add_base_node_overlay.button.confirm"), type: .normal, callback: { try? Tari.shared.connection.addBaseNode(name: name, peer: peer) }), PopUpDialogButtonModel(title: localized("common.close"), type: .text) ]) diff --git a/MobileWallet/Common/Errors/WalletError.swift b/MobileWallet/Common/Errors/WalletError.swift index ea36efbb..d99a25db 100644 --- a/MobileWallet/Common/Errors/WalletError.swift +++ b/MobileWallet/Common/Errors/WalletError.swift @@ -47,13 +47,21 @@ struct WalletError: CoreError { self.code = Int(code) } + static var notEnoughFunds: Self { WalletError(code: 101) } static var databaseDataError: Self { WalletError(code: 114) } + static var fundsPending: Self { WalletError(code: 115) } static var transactionNotFound: Self { WalletError(code: 204) } static var contactNotFound: Self { WalletError(code: 401) } static var invalidPassphraseEncryptionCypher: Self { WalletError(code: 420) } + static var valuesNotFound: Self { WalletError(code: 424) } static var invalidPassphrase: Self { WalletError(code: 428) } static var seedWordsInvalidData: Self { WalletError(code: 429) } static var seedWordsVersionMismatch: Self { WalletError(code: 430) } static var unknown: Self { WalletError(code: -1) } + + static func ~=(lhs: Self, rhs: Error) -> Bool { + guard let rhs = rhs as? Self else { return false } + return lhs == rhs + } } diff --git a/MobileWallet/Common/Extensions/LAContext.swift b/MobileWallet/Common/Extensions/LAContext.swift index 74b6a702..d8998236 100644 --- a/MobileWallet/Common/Extensions/LAContext.swift +++ b/MobileWallet/Common/Extensions/LAContext.swift @@ -97,7 +97,7 @@ extension LAContext { } else { if !showFailedDialog { return } let localizedReason = error?.localizedDescription ?? localized("authentication.fail.description") - TariLogger.error("Biometrics auth failed", error: error) + Logger.log(message: "Biometrics auth failed: \(error?.localizedDescription)", domain: .general, level: .error) self.authenticationFailedAlertOptions(reason: localizedReason, onSuccess: onSuccess) } } diff --git a/MobileWallet/Common/Extensions/Publisher+Binding.swift b/MobileWallet/Common/Extensions/Publisher+Binding.swift new file mode 100644 index 00000000..eb88e4e6 --- /dev/null +++ b/MobileWallet/Common/Extensions/Publisher+Binding.swift @@ -0,0 +1,47 @@ +// Publisher+Binding.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +extension Publisher where Failure == Never { + func assignPublisher(to keyPath: ReferenceWritableKeyPath, on object: T) -> AnyCancellable { + sink { [weak object] in object?[keyPath: keyPath] = $0 } + } +} diff --git a/MobileWallet/Common/Managers/ErrorMessageManager.swift b/MobileWallet/Common/Managers/ErrorMessageManager.swift index 805dfab9..095648c9 100644 --- a/MobileWallet/Common/Managers/ErrorMessageManager.swift +++ b/MobileWallet/Common/Managers/ErrorMessageManager.swift @@ -53,8 +53,14 @@ enum ErrorMessageManager { switch error { case let error as WalletError: return model(walletError: error) - case let error as SeedWords.Error: + case let error as FFIWalletManager.GeneralError: + return model(internalWalletError: error) + case let error as SeedWords.InternalError: return model(seedWordsError: error) + case let error as TorManager.TorError: + return model(torError: error) + case let error as TariFaucetService.InternalError: + return model(faucetError: error) default: return genericErrorModel } @@ -76,7 +82,7 @@ enum ErrorMessageManager { return MessageModel(title: genericErrorModel.title, message: message?.appending(signature: walletError.signature), type: .error) } - private static func model(seedWordsError: SeedWords.Error) -> MessageModel { + private static func model(seedWordsError: SeedWords.InternalError) -> MessageModel { let message: String @@ -93,6 +99,46 @@ enum ErrorMessageManager { return MessageModel(title: localized("restore_from_seed_words.error.title"), message: message.appending(signature: seedWordsError.signature), type: .error) } + + private static func model(torError: TorManager.TorError) -> MessageModel { + + let message: String + + switch torError { + case .connectionFailed: + message = localized("Onion_Error.error.invalid_bridges") + case .missingCookie: + message = localized("Onion_Error.error.missing_cookie_file") + case .connectionTimeout: + message = localized("Onion_Error.error.connectionError") + } + + return MessageModel(title: localized("Onion_Error.error.title.onionError"), message: message, type: .error) + } + + private static func model(internalWalletError: FFIWalletManager.GeneralError) -> MessageModel { + + let message: String + + switch internalWalletError { + case .unableToCreateWallet: + message = localized("wallet.error.wallet_not_initialized") + } + + return MessageModel(title: genericErrorModel.title, message: message, type: .error) + } + + private static func model(faucetError: TariFaucetService.InternalError) -> MessageModel { + + let message: String + + switch faucetError { + case .invalidSignatureAndNonceString: + message = localized("wallet.error.invalid_signature") + } + + return MessageModel(title: genericErrorModel.title, message: message, type: .error) + } } private extension String { diff --git a/MobileWallet/Common/Managers/ShortcutsManager.swift b/MobileWallet/Common/Managers/ShortcutsManager.swift index b5c41046..c869e4fb 100644 --- a/MobileWallet/Common/Managers/ShortcutsManager.swift +++ b/MobileWallet/Common/Managers/ShortcutsManager.swift @@ -63,7 +63,7 @@ final class ShortcutsManager { let sendShortcutItem = UIApplicationShortcutItem( type: ShortcutType.send.rawValue, - localizedTitle: localized("common.send.with_param"), + localizedTitle: localized("common.send.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol), localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "Gem"), userInfo: nil diff --git a/MobileWallet/Common/Managers/WalletTransactionsManager.swift b/MobileWallet/Common/Managers/WalletTransactionsManager.swift index e7fe5d62..ced34cc9 100644 --- a/MobileWallet/Common/Managers/WalletTransactionsManager.swift +++ b/MobileWallet/Common/Managers/WalletTransactionsManager.swift @@ -46,7 +46,6 @@ final class WalletTransactionsManager { case transactionError(error: Error) case unsucessfulTransaction case noInternetConnection - case unableToStartWallet case timeout } @@ -57,7 +56,7 @@ final class WalletTransactionsManager { // MARK: - Properties - private let connectionTimeout: TimeInterval = 30.0 + private let connectionTimeout: DispatchQueue.SchedulerTimeType.Stride = .seconds(30) private var cancellables = Set() // MARK: - Actions @@ -72,7 +71,7 @@ final class WalletTransactionsManager { if !isOneSidedPayment { subject.send(.transaction) } - self?.verifyWalletStateAndSendTransactionToBlockchain(publicKey: publicKey, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) { result in + self?.sendTransactionToBlockchain(publicKey: publicKey, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) { result in switch result { case .success: subject.send(completion: .finished) @@ -90,107 +89,75 @@ final class WalletTransactionsManager { private func waitForConnection(result: @escaping (Result) -> Void) { - let connectionState = LegacyConnectionMonitor.shared.state - - switch connectionState.reachability { - case .offline, .unknown: + guard case .connected = Tari.shared.connectionMonitor.networkConnection else { result(.failure(.noInternetConnection)) - case .wifi, .cellular: - break - } - - let startDate = Date() - - Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in - - if connectionState.torStatus == .connected, connectionState.torBootstrapProgress == 100 { - timer.invalidate() - result(.success) - } - - guard let self = self, -startDate.timeIntervalSinceNow > self.connectionTimeout else { return } - - timer.invalidate() - result(.failure(.timeout)) + return } - } - - private func verifyWalletStateAndSendTransactionToBlockchain(publicKey: PublicKey, amount: MicroTari, feePerGram: MicroTari, message: String, isOneSidedPayment: Bool, result: @escaping (Result) -> Void) { - - var cancel: AnyCancellable? - - cancel = TariLib.shared.walletStatePublisher - .receive(on: RunLoop.main) - .sink { [weak self] walletState in - switch walletState { - case .started: - cancel?.cancel() - self?.sendTransactionToBlockchain(publicKey: publicKey, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment, result: result) - case .startFailed: - cancel?.cancel() - result(.failure(.unableToStartWallet)) - case .notReady, .starting: - break - } - } - - cancel?.store(in: &cancellables) + + Publishers.CombineLatest(Tari.shared.connectionMonitor.$torConnection, Tari.shared.connectionMonitor.$isTorBootstrapCompleted) + .filter { $0 == .connected && $1 } + .timeout(connectionTimeout, scheduler: DispatchQueue.global()) + .first() + .sink( + receiveCompletion: { + guard case .failure = $0 else { return } + result(.failure(.timeout)) + }, + receiveValue: { _ in result(.success) } + ) + .store(in: &cancellables) } private func sendTransactionToBlockchain(publicKey: PublicKey, amount: MicroTari, feePerGram: MicroTari, message: String, isOneSidedPayment: Bool, result: @escaping (Result) -> Void) { - - guard let wallet = TariLib.shared.tariWallet else { return } do { - let transactionID = try wallet.sendTx(destination: publicKey, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) + let transactionID = try Tari.shared.transactions.send( + toPublicKey: publicKey, + amount: amount.rawValue, + feePerGram: feePerGram.rawValue, + message: message, + isOneSidedPayment: isOneSidedPayment + ) guard !isOneSidedPayment else { result(.success) return } - startListeningForWalletEvents(transactionID: transactionID, publicKey: publicKey, result: result) + startListeningForWalletEvents(transactionID: transactionID, recipientHex: try publicKey.byteVector.hex, result: result) } catch { result(.failure(.transactionError(error: error))) } } - private func startListeningForWalletEvents(transactionID: UInt64, publicKey: PublicKey, result: @escaping (Result) -> Void) { + private func startListeningForWalletEvents(transactionID: UInt64, recipientHex: String, result: @escaping (Result) -> Void) { - TariEventBus.events(forType: .transactionSendResult) - .compactMap { $0.object as? TransactionResult } - .filter { $0.id == transactionID } + WalletCallbacksManager.shared.transactionSendResult + .filter { $0.identifier == transactionID } + .first() .sink { [weak self] in - - self?.cancelWalletEvents() - guard $0.status.isSuccess else { result(.failure(.unsucessfulTransaction)) return } - self?.sendPushNotificationToRecipient(publicKey: publicKey) + self?.sendPushNotificationToRecipient(recipientHex: recipientHex) - TariLogger.info("Transaction send successful.") - Tracker.shared.track(eventWithCategory: "Transaction", action: "Transaction Accepted") + Logger.log(message: "Transaction send successful", domain: .general, level: .info) result(.success) } .store(in: &cancellables) } - private func sendPushNotificationToRecipient(publicKey: PublicKey) { + private func sendPushNotificationToRecipient(recipientHex: String) { do { try NotificationManager.shared.sendToRecipient( - publicKey, - onSuccess: { TariLogger.info("Recipient has been notified") }, - onError: { TariLogger.error("Failed to notify recipient", error: $0) } + recipientHex: recipientHex, + onSuccess: { Logger.log(message: "Recipient has been notified", domain: .general, level: .info) }, + onError: { Logger.log(message: "Failed to notify recipient: \($0.localizedDescription)", domain: .general, level: .error) } ) } catch { - TariLogger.error("Failed to notify recipient", error: error) + Logger.log(message: "Failed to notify recipient: \(error.localizedDescription)", domain: .general, level: .error) } } - - private func cancelWalletEvents() { - cancellables.forEach { $0.cancel() } - } } diff --git a/MobileWallet/Common/NotificationManager.swift b/MobileWallet/Common/NotificationManager.swift index e2273793..75c4d27f 100644 --- a/MobileWallet/Common/NotificationManager.swift +++ b/MobileWallet/Common/NotificationManager.swift @@ -87,24 +87,17 @@ final class NotificationManager { private let options: UNAuthorizationOptions = [.alert, .sound, .badge] private var hasRegisteredToken: Bool { UserDefaults.standard.bool(forKey: NotificationManager.hasRegisteredTokenKey) } - private var cancelables = Set() + private var cancellables = Set() private init() { setupWalletStateHandler() } - private func setupWalletStateHandler() { - - TariLib.shared.walletStatePublisher - .sink { [weak self] in - switch $0 { - case .started: - self?.requestAuthorization() - case .starting, .notReady, .startFailed: - break - } - } - .store(in: &cancelables) + func setupWalletStateHandler() { + NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) + .filter { _ in Tari.shared.isWalletExist } + .sink { [weak self] _ in self?.requestAuthorization() } + .store(in: &cancellables) } func requestAuthorization(_ completionHandler: ((Bool) -> Void)? = nil) { @@ -115,42 +108,39 @@ final class NotificationManager { } guard !hasRegisteredToken else { - TariLogger.verbose("Already registered for push notifications") + Logger.log(message: "Already registered for push notifications", domain: .general, level: .info) completionHandler?(true) return } - notificationCenter.requestAuthorization(options: options) { _, error in - guard error == nil else { - TariLogger.error("NotificationManager request authorization", error: error) - completionHandler?(false) + notificationCenter.requestAuthorization(options: options) { [weak self] _, error in + guard let error else { + self?.registerWithAPNS(completionHandler) return } - - self.registerWithAPNS(completionHandler) + Logger.log(message: "NotificationManager request authorization: \(error.localizedDescription)", domain: .general, level: .error) + completionHandler?(false) } } private func registerWithAPNS(_ completionHandler: ((Bool) -> Void)? = nil) { - TariLogger.verbose("Checking notification settings") + Logger.log(message: "Checking notification settings", domain: .general, level: .info) notificationCenter.getNotificationSettings { (settings) in if settings.authorizationStatus == .authorized { completionHandler?(true) - - TariLogger.info("Notifications authorized") + Logger.log(message: "Notifications authorized", domain: .general, level: .info) DispatchQueue.main.async { - TariLogger.info("Registering for remote notifications with Apple") + Logger.log(message: "Registering for remote notifications with Apple", domain: .general, level: .info) UIApplication.shared.registerForRemoteNotifications() } } else { - TariLogger.warn("Notifications not authorized") + Logger.log(message: "Notifications not authorized", domain: .general, level: .warning) completionHandler?(false) } } } func handleForegroundNotification(_ notification: UNNotification, completionHandler: (UNNotificationPresentationOptions) -> Void) { - try? TariLib.shared.tariWallet?.syncBaseNode() if notification.request.identifier == NotificationIdentifier.scheduledBackupFailure.rawValue { completionHandler([.alert, .badge, .sound]) } @@ -172,7 +162,7 @@ final class NotificationManager { notificationCenter.add(request) { (error) in if let error = error { - TariLogger.error("Scheduling local push notification", error: error) + Logger.log(message: "Scheduling local push notification: \(error)", domain: .general, level: .error) onCompletion?(false) } else { onCompletion?(true) @@ -192,82 +182,65 @@ final class NotificationManager { func registerDeviceToken(_ deviceToken: Data) { let apnsDeviceToken = deviceToken.map {String(format: "%02.2hhx", $0)}.joined() - TariLogger.verbose("Registering device token with public key") + Logger.log(message: "Registering device token with public key", domain: .general, level: .verbose) do { - let signature = try signRequestMessage(apnsDeviceToken) + let messageData = try sign(message: apnsDeviceToken) let requestPayload = try JSONEncoder().encode( TokenRegistrationServerRequest( token: apnsDeviceToken, - signature: signature.hex, - public_nonce: signature.nonce + signature: messageData.metadata.hex, + public_nonce: messageData.metadata.nonce ) ) pushServerRequest( - path: "/register/\(signature.publicKey.hex.0)", + path: "/register/\(messageData.hex)", requestPayload: requestPayload, onSuccess: { UserDefaults.standard.set(true, forKey: NotificationManager.hasRegisteredTokenKey) - TariLogger.info("Registered device token") + Logger.log(message: "Registered device token", domain: .general, level: .info) }) { (error) in - TariLogger.error("Failed to register device token", error: error) + Logger.log(message: "Failed to register device token: \(error.localizedDescription)", domain: .general, level: .error) } } catch { - TariLogger.error("Failed to register device token. Will attempt again later.", error: error) + Logger.log(message: "Failed to register device token. Will attempt again later: \(error.localizedDescription)", domain: .general, level: .error) } } - func sendToRecipient(_ toPublicKey: PublicKey, onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { - let signature = try signRequestMessage(toPublicKey.hex.0) + func sendToRecipient(recipientHex: String, onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { + let messageData = try sign(message: recipientHex) let requestPayload = try JSONEncoder().encode( SendNotificationServerRequest( - from_pub_key: signature.publicKey.hex.0, - signature: signature.hex, - public_nonce: signature.nonce + from_pub_key: messageData.hex, + signature: messageData.metadata.hex, + public_nonce: messageData.metadata.nonce ) ) - pushServerRequest(path: "/send/\(toPublicKey.hex.0)", requestPayload: requestPayload, onSuccess: onSuccess, onError: onError) + pushServerRequest(path: "/send/\(recipientHex)", requestPayload: requestPayload, onSuccess: onSuccess, onError: onError) } // TODO remove this is local push notifications work better func cancelReminders(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { - let signature = try signRequestMessage("cancel-reminders") - + let messageData = try sign(message: "cancel-reminders") let requestPayload = try JSONEncoder().encode( CancelRemindersServerRequest( - pub_key: signature.publicKey.hex.0, - signature: signature.hex, - public_nonce: signature.nonce + pub_key: messageData.hex, + signature: messageData.metadata.hex, + public_nonce: messageData.metadata.nonce ) ) pushServerRequest(path: "/cancel-reminders", requestPayload: requestPayload, onSuccess: onSuccess, onError: onError) } - - private func signRequestMessage(_ message: String) throws -> Signature { - guard let wallet = TariLib.shared.tariWallet else { - throw WalletErrors.walletNotInitialized - } - - let (pubKey, pubKeyError) = wallet.publicKey - guard pubKeyError == nil else { - throw pubKeyError! - } - - let (pubKeyHex, hexError) = pubKey!.hex - guard hexError == nil else { - throw hexError! - } - - guard let apiKey = TariSettings.shared.pushServerApiKey else { - throw PushNotificationServerError.missingApiKey - } - - // TODO add apiKey when new push server redeployed - return try wallet.signMessage("\(apiKey)\(pubKeyHex)\(message)") + + private func sign(message: String) throws -> (hex: String, metadata: MessageMetadata) { + let publicKeyHex = try Tari.shared.walletPublicKey.byteVector.hex + guard let apiKey = TariSettings.shared.pushServerApiKey else { throw PushNotificationServerError.missingApiKey } + let metadata = try Tari.shared.faucet.sign(message: "\(apiKey)\(publicKeyHex)\(message)") + return (hex: publicKeyHex, metadata: metadata) } private func pushServerRequest(path: String, requestPayload: Data, onSuccess: @escaping () -> Void, onError: @escaping (Error) -> Void) { diff --git a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift index 2179c223..aa064fc5 100644 --- a/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift +++ b/MobileWallet/Common/Pop-up/PopUpPresenter+CommonPopUps.swift @@ -126,18 +126,20 @@ extension PopUpPresenter { } private static func log(message: MessageModel) { - var log = "Pop-up Title=\(message.title)" + var log = "Pop-up Title: \(message.title)" if let description = message.message { - log += " Message=\(description)" + log += " Message: \(description)" } switch message.type { case .normal: - TariLogger.info(log) + log += " Type: Normal" case .error: - TariLogger.error(log) + log += " Type: Error" } + + Logger.log(message: log, domain: .userInterface, level: .info) } // MARK: - Helpers diff --git a/MobileWallet/Common/Theme.swift b/MobileWallet/Common/Theme.swift index 515686b4..8192b41e 100644 --- a/MobileWallet/Common/Theme.swift +++ b/MobileWallet/Common/Theme.swift @@ -104,11 +104,6 @@ struct Colors: Loopable { let checkBoxBorderColor = UIColor(named: "CheckboxBorderColor") - // Splash - let splashTitle = UIColor(named: "SplashTitle") - let splashSubtitle = UIColor(named: "SplashSubtitle") - let splashVersionLabel = UIColor(named: "SplashVersionLabel") - // SplashCreatingWallet let creatingWalletFirstLabel = UIColor(named: "CreatingWalletBlackLabel") let creatingWalletSecondLabel = UIColor(named: "CreatingWalletBlackLabel") @@ -143,7 +138,6 @@ struct Colors: Loopable { let settingsPasswordWarning = UIColor(named: "SettingsPasswordWarning") // Home screen - let homeScreenBackground = UIColor(named: "HomeScreenBackground") let homeScreenTotalBalanceLabel = UIColor(named: "HomeScreenTotalBalanceLabel") let homeScreenTotalBalanceValueLabel = UIColor(named: "HomeScreenTotalBalanceLabel") let floatingPanelGrabber = UIColor(named: "FloatingPanelGrabber") @@ -376,15 +370,10 @@ struct Images: Loopable { } struct Fonts: Loopable { - let splashVersionFooterLabel = UIFont.Avenir.heavy.withSize(9.0) + let actionButton = UIFont.Avenir.heavy.withSize(16.0) let copiedLabel = UIFont.Avenir.black.withSize(13.0) - // Splash - let splashTitleLabel = UIFont.Avenir.black.withSize(30.0) - let splashSubtitleLabel = UIFont.Avenir.medium.withSize(14.0) - let splashDisclaimerLabel = UIFont.Avenir.medium.withSize(12.0) - // SplashCreatingWallet let createWalletFirstLabel = UIFont.Avenir.black.withSize(18.0) let createWalletSecondLabelFirstText = UIFont.Avenir.black.withSize(18.0) diff --git a/MobileWallet/Common/Toasts/ToastPresenter.swift b/MobileWallet/Common/Toasts/ToastPresenter.swift index 930626ad..97b567fc 100644 --- a/MobileWallet/Common/Toasts/ToastPresenter.swift +++ b/MobileWallet/Common/Toasts/ToastPresenter.swift @@ -57,6 +57,6 @@ enum ToastPresenter { SwiftEntryKit.display(entry: toast, using: attributes) UIApplication.shared.hideKeyboard() - TariLogger.verbose("Success Toast: title=\(title)") + Logger.log(message: "Success Toast: \(title)", domain: .userInterface, level: .info) } } diff --git a/MobileWallet/MobileWallet-bridging-header.h b/MobileWallet/MobileWallet-bridging-header.h index c418d2b3..93045b14 100644 --- a/MobileWallet/MobileWallet-bridging-header.h +++ b/MobileWallet/MobileWallet-bridging-header.h @@ -4,4 +4,4 @@ // #import "./TariLib/wallet.h" -#import "./TariLib/Tor/Helpers/NetworkTools.h" +#import "./TariLib/Core/Tor/Helpers/NetworkTools.h" diff --git a/MobileWallet/SceneDelegate.swift b/MobileWallet/SceneDelegate.swift index dfa2bd0f..ffd184fc 100644 --- a/MobileWallet/SceneDelegate.swift +++ b/MobileWallet/SceneDelegate.swift @@ -45,9 +45,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - func scene(_ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions) { + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). @@ -81,18 +79,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } setupYatIntegration() - - guard let _ = (scene as? UIWindowScene) else { return } - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - let navigationController = AlwaysPoppableNavigationController( - rootViewController: SplashViewController() - ) - navigationController.setNavigationBarHidden(true, animated: false) - window.rootViewController = navigationController - self.window = window - window.makeKeyAndVisible() - } + + guard let windowScene = scene as? UIWindowScene else { return } + window = UIWindow(windowScene: windowScene) + window?.makeKeyAndVisible() + AppRouter.transitionToSplashScreen(animated: false) } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { @@ -105,34 +96,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ShortcutsManager.executeQueuedShortcut() } - private func onTorSuccess(_ onComplete: @escaping () -> Void) { - // Handle if tor ports opened later - TariEventBus.onMainThread(self, eventType: .torPortsOpened) { [weak self] (_) in - guard let self = self else { return } - TariEventBus.unregister(self, eventType: .torPortsOpened) - onComplete() - } - if TariLib.shared.torPortsOpened { - TariEventBus.unregister(self, eventType: .torPortsOpened) - onComplete() - } - } - func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. // Remove badges from push notifications UIApplication.shared.applicationIconBadgeNumber = 0 - LegacyConnectionMonitor.shared.start() - TariLib.shared.startTor() - // Only starts the wallet if it was stopped. Else wallet is started on the splash screen. - if TariLib.shared.isWalletExist { - onTorSuccess { - guard TariLib.shared.walletState == .notReady else { return } - TariLib.shared.startWallet(seedWords: nil) - } - } + if UserDefaults.Key.backupOperationAborted.boolValue() && ICloudBackup.shared.iCloudBackupsIsOn && !ICloudBackup.shared.inProgress { @@ -147,9 +117,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Save changes in the application's managed object context when the application transitions to the background. (UIApplication.shared.delegate as? AppDelegate)?.saveContext() - LegacyConnectionMonitor.shared.stop() - TariLib.shared.stopWallet() - TariLib.shared.stopTor() ICloudBackup.shared.backgroundBackupWallet() } diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashView.swift b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift new file mode 100644 index 00000000..e065a7bf --- /dev/null +++ b/MobileWallet/Screens/AppEntry/Splash/SplashView.swift @@ -0,0 +1,285 @@ +// SplashView.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 21/07/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Lottie +import TariCommon +import UIKit + +final class SplashView: UIView { + + // MARK: - Subviews + + @View private var animatedLogoView: AnimationView = { + let view = AnimationView() + view.animation = Animation.named(.splash) + view.backgroundBehavior = .pauseAndRestore + return view + }() + + @View private var videoView: VideoView = { + let view = VideoView() + view.url = Bundle.main.url(forResource: "purple_orb", withExtension: "mp4") + return view + }() + + @View private var titleLabel: UILabel = { + let view = UILabel() + view.text = localized("splash.title") + view.font = .Avenir.black.withSize(30.0) + view.interlineSpacing(spacingValue: 0) + view.textColor = .tari.white + view.textAlignment = .center + view.numberOfLines = 2 + view.adjustsFontSizeToFitWidth = true + return view + }() + + @View private var createWalletButton: ActionButton = ActionButton() + @View private var selectNetworkButton = ActionButton() + + @View private var restoreWalletButton: TextButton = { + let view = TextButton() + view.setTitle(localized("splash.restore_wallet"), for: .normal) + return view + }() + + @View private var disclaimerTextView: UnselectableTextView = { + let view = UnselectableTextView() + view.backgroundColor = .clear + view.isScrollEnabled = false + view.isEditable = false + return view + }() + + @View private var tariIconView: UIImageView = { + let view = UIImageView() + view.image = Theme.shared.images.currencySymbol + return view + }() + + @View private var versionLabel: UILabel = { + let view = UILabel() + view.font = .Avenir.heavy.withSize(9.0) + view.textColor = .tari.greys.mediumGrey + view.textAlignment = .center + return view + }() + + // MARK: - Properties + + var createWalletButtonTitle: String? { + get { createWalletButton.title(for: .normal) } + set { createWalletButton.setTitle(newValue, for: .normal) } + } + + var isCreateWalletButtonSpinnerVisible: Bool = false { + didSet { createWalletButton.variation = isCreateWalletButtonSpinnerVisible ? .loading : .normal } + } + + var selectNetworkButtonTitle: String? { + get { selectNetworkButton.title(for: .normal) } + set { selectNetworkButton.setTitle(newValue, for: .normal) } + } + + var versionText: String? { + get { versionLabel.text } + set { versionLabel.text = newValue } + } + + var onCreateWalletButtonTap: (() -> Void)? + var onSelectNetworkButtonTap: (() -> Void)? + var onRestoreWalletButtonTap: (() -> Void)? + + private var idleLogoConstraint: NSLayoutConstraint? + private var walletCreatedLogoConstraint: NSLayoutConstraint? + + // MARK: - Initialisers + + init() { + super.init(frame: .zero) + setupViews() + setupDisclamerView() + setupConstraints() + setupCallbacks() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setups + + private func setupViews() { + backgroundColor = .tari.greys.black + } + + private func setupConstraints() { + + [videoView, titleLabel, createWalletButton, selectNetworkButton, restoreWalletButton, disclaimerTextView, tariIconView, versionLabel, animatedLogoView].forEach(addSubview) + + let idleLogoConstraint = animatedLogoView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 19.0) + self.idleLogoConstraint = idleLogoConstraint + walletCreatedLogoConstraint = animatedLogoView.centerYAnchor.constraint(equalTo: centerYAnchor) + + let constraints = [ + idleLogoConstraint, + animatedLogoView.widthAnchor.constraint(equalToConstant: 145.0), + animatedLogoView.heightAnchor.constraint(equalToConstant: 30.0), + animatedLogoView.centerXAnchor.constraint(equalTo: centerXAnchor), + videoView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 49.0), + videoView.leadingAnchor.constraint(equalTo: leadingAnchor), + videoView.trailingAnchor.constraint(equalTo: trailingAnchor), + titleLabel.topAnchor.constraint(equalTo: videoView.bottomAnchor, constant: 15.0), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), + createWalletButton.topAnchor.constraint(greaterThanOrEqualTo: titleLabel.bottomAnchor, constant: 5.0), + createWalletButton.topAnchor.constraint(lessThanOrEqualTo: titleLabel.bottomAnchor, constant: 25.0), + createWalletButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + createWalletButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), + selectNetworkButton.topAnchor.constraint(equalTo: createWalletButton.bottomAnchor, constant: 12.0), + selectNetworkButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + selectNetworkButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), + selectNetworkButton.heightAnchor.constraint(equalToConstant: 32.0), + restoreWalletButton.topAnchor.constraint(greaterThanOrEqualTo: selectNetworkButton.bottomAnchor, constant: 5.0), + restoreWalletButton.topAnchor.constraint(lessThanOrEqualTo: selectNetworkButton.bottomAnchor, constant: 22.0), + restoreWalletButton.centerXAnchor.constraint(equalTo: centerXAnchor), + restoreWalletButton.heightAnchor.constraint(equalToConstant: 25.0), + disclaimerTextView.topAnchor.constraint(greaterThanOrEqualTo: restoreWalletButton.bottomAnchor, constant: 0.0), + disclaimerTextView.topAnchor.constraint(lessThanOrEqualTo: restoreWalletButton.bottomAnchor, constant: 5.0), + disclaimerTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 22.0), + disclaimerTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -22.0), + tariIconView.topAnchor.constraint(greaterThanOrEqualTo: disclaimerTextView.bottomAnchor, constant: 0.0), + tariIconView.topAnchor.constraint(lessThanOrEqualTo: disclaimerTextView.bottomAnchor, constant: 12.0), + tariIconView.centerXAnchor.constraint(equalTo: centerXAnchor), + tariIconView.widthAnchor.constraint(equalToConstant: 24.0), + tariIconView.heightAnchor.constraint(equalToConstant: 24.0), + versionLabel.topAnchor.constraint(equalTo: tariIconView.bottomAnchor, constant: 9.0), + versionLabel.centerXAnchor.constraint(equalTo: centerXAnchor), + versionLabel.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -12.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + private func setupCallbacks() { + + createWalletButton.onTap = { [weak self] in + self?.onCreateWalletButtonTap?() + } + + selectNetworkButton.onTap = { [weak self] in + self?.onSelectNetworkButtonTap?() + } + + restoreWalletButton.onTap = { [weak self] in + self?.onRestoreWalletButtonTap?() + } + } + + private func setupDisclamerView() { + + guard let textColor = UIColor.tari.greys.mediumGrey else { return } + + disclaimerTextView.linkTextAttributes = [ + NSAttributedString.Key.foregroundColor: textColor, + NSAttributedString.Key.underlineColor: textColor, + NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue + ] + + let userAgreementLinkText = localized("splash.disclaimer.param.user_agreement") + let privacyPolicyLinkText = localized("splash.disclaimer.param.privacy_policy") + let text = String(format: localized("splash.disclaimer.with_params"), userAgreementLinkText, privacyPolicyLinkText) + + let attributedText = NSMutableAttributedString(string: text) + + if let userAgreementStartIndex = text.indexDistance(of: userAgreementLinkText) { + let range = NSRange(location: userAgreementStartIndex, length: userAgreementLinkText.count) + attributedText.addAttribute(.link, value: TariSettings.shared.userAgreementUrl, range: range) + } + + if let privacyPolicyStartIndex = text.indexDistance(of: privacyPolicyLinkText) { + let range = NSRange(location: privacyPolicyStartIndex, length: privacyPolicyLinkText.count) + attributedText.addAttribute(.link, value: TariSettings.shared.privacyPolicyUrl, range: range) + } + + disclaimerTextView.attributedText = attributedText + disclaimerTextView.textColor = textColor + disclaimerTextView.textAlignment = .center + disclaimerTextView.font = .Avenir.medium.withSize(12.0) + } + + // MARK: - Actions + + func updateLayout(showInterface: Bool, animated: Bool, completion: (() -> Void)? = nil) { + + if showInterface { + walletCreatedLogoConstraint?.isActive = false + idleLogoConstraint?.isActive = true + } else { + idleLogoConstraint?.isActive = false + walletCreatedLogoConstraint?.isActive = true + } + + let alpha = showInterface ? 3.0 : 0.0 + + let transition = { + self.layoutIfNeeded() + self.videoView.alpha = alpha + self.titleLabel.alpha = alpha + self.createWalletButton.alpha = alpha + self.selectNetworkButton.alpha = alpha + self.restoreWalletButton.alpha = alpha + self.disclaimerTextView.alpha = alpha + self.tariIconView.alpha = alpha + self.versionLabel.alpha = alpha + } + + if animated { + UIView.animate(withDuration: 1.0, animations: transition, completion: { _ in completion?() }) + } else { + transition() + completion?() + } + } + + func playLogoAnimation(completion: @escaping () -> Void) { + animatedLogoView.play(completion: { _ in completion() }) + } +} \ No newline at end of file diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift index 1db64691..bb246eaa 100644 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewController.swift @@ -39,363 +39,153 @@ */ import UIKit -import Lottie import LocalAuthentication -import SwiftEntryKit -import AVFoundation import Combine -class SplashViewController: UIViewController, UITextViewDelegate { - - private enum AnimationType { - case scaleDownLogo - case squashLetters +final class SplashViewController: UIViewController { + + // MARK: - Properties + + private let model = SplashViewModel() + private let mainView = SplashView() + private let authenticationContext = LAContext() + + private var cancellables = Set() + private var animateTransitions = false + + // MARK: - View Lifecycle + + override func loadView() { + view = mainView } - - // MARK: - Variables and constants - private let localAuthenticationContext = LAContext() - var ticketTopLayoutConstraint: NSLayoutConstraint? - var ticketBottom: NSLayoutConstraint? - var alreadyReplacedVideo: Bool = false - - // MARK: - Outlets - let generalContainer = UIView() - let videoView = VideoView() - let versionLabel = UILabel() - let animationContainer = AnimationView() - let elementsContainer = UIView() - let createWalletButton = ActionButton() - let selectNetworkButton = ActionButton() - let titleLabel = UILabel() - let gemImageView = UIImageView() - let disclaimerText = UITextView() - let restoreButton = UIButton() - - var distanceTitleSubtitle = NSLayoutConstraint() - var animationContainerBottomAnchor: NSLayoutConstraint? - var animationContainerBottomAnchorToVideo: NSLayoutConstraint? - - private var cancelables = Set() - - // MARK: - Override functions + override func viewDidLoad() { super.viewDidLoad() - setupView() - setupFeedbacks() - loadAnimation() - handleWalletEvents() - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - setupVideoAnimation() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - startOnboardingFlow(animationType: .squashLetters) + setupCallbacks() } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - SwiftEntryKit.dismiss() - } - - private func startOnboardingFlow(animationType: AnimationType) { - guard !TariSettings.shared.isUnitTesting else { return } - titleAnimation() - checkExistingWallet(animationType: animationType) - } - - private func handleWalletEvents() { - // Handle tor progress - TariEventBus.onMainThread(self, eventType: .torConnectionProgress) { - [weak self] (result) in - guard let self = self else { return } - - if let progress: Int = result?.object as? Int { - if progress == 0 { - var attributes = EKAttributes.topToast - attributes.entryBackground = .color(color: EKColor(Theme.shared.colors.successFeedbackPopupBackground!)) - attributes.screenBackground = .clear - attributes.shadow = .active( - with: .init( - color: EKColor(Theme.shared.colors.feedbackPopupBackground!), - opacity: 0.35, - radius: 10, - offset: .zero - ) - ) - attributes.displayDuration = .infinity - attributes.screenInteraction = .forward - } - } - } - - TariEventBus.onMainThread(self, eventType: .torConnectionFailed) { [weak self] (result) in - guard let _ = self else { return } - PopUpPresenter.show(message: MessageModel(title: localized("tor.error.title"), message: localized("tor.error.description"), type: .error)) - } - } - - private func setupFeedbacks() { + + // MARK: - Setups + + private func setupCallbacks() { - NetworkManager.shared.$selectedNetwork + model.$status + .compactMap { $0 } .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.update(network: $0) } - .store(in: &cancelables) + .sink { [weak self] in self?.handle(status: $0) } + .store(in: &cancellables) - NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) - .sink { [weak self] _ in self?.videoView.startPlayer() } - .store(in: &cancelables) - } - - private func onTorSuccess(_ onComplete: @escaping () -> Void) { - // Handle if tor ports opened later - TariEventBus.onMainThread(self, eventType: .torPortsOpened) { [weak self] (_) in - guard let self = self else { return } - TariEventBus.unregister(self, eventType: .torPortsOpened) - onComplete() - } - if TariLib.shared.torPortsOpened { - TariEventBus.unregister(self, eventType: .torPortsOpened) - onComplete() - } - } - - private func prepareEnviroment(animationType: AnimationType) { - - let dispatchGroup = DispatchGroup() - var error: WalletError? - - dispatchGroup.enter() - onTorSuccess { dispatchGroup.leave() } - - dispatchGroup.enter() - waitForWalletStart( - onComplete: { dispatchGroup.leave() }, - onError: { - error = $0 - dispatchGroup.leave() - } - ) - - dispatchGroup.notify(queue: .main) { [weak self] in - - guard let error = error else { - self?.navigateToHome(animationType: animationType) - return - } - - let errorMessage = ErrorMessageManager.errorMessage(forError: error) - - PopUpPresenter.showMessageWithCloseButton(message: MessageModel(title: localized("splash.wallet_error.title"), message: errorMessage, type: .error)) { [weak self] in - self?.updateCreateWalletButtonState() - self?.resetView() - } + model.$networkName + .compactMap { $0 } + .map { localized("splash.button.select_network", arguments: $0) } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.selectNetworkButtonTitle = $0 } + .store(in: &cancellables) + + model.$appVersion + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.versionText = $0 } + .store(in: &cancellables) + + model.$isWalletExist + .map { $0 ? localized("splash.button.open_wallet") : localized("splash.button.create_wallet") } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.mainView.createWalletButtonTitle = $0 } + .store(in: &cancellables) + + model.$errorMessage + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) + + mainView.onCreateWalletButtonTap = { [weak self] in + self?.model.startWallet() } - } - - private func navigateToHome(animationType: AnimationType) { - switch animationType { - case .scaleDownLogo: - topAnimationAndRemoveVideoAnimation { [weak self] in self?.navigateToHome() } - case .squashLetters: - startAnimation { [weak self] in self?.navigateToHome() } + + mainView.onSelectNetworkButtonTap = { [weak self] in + self?.showNetworkListPopup() } - } - - private func checkExistingWallet(animationType: AnimationType) { - if TariLib.shared.isWalletExist { - startWalletIfNeeded() - // Authenticate user -> start animation -> wait for tor -> start wallet -> navigate to home - localAuthenticationContext.authenticateUser { [weak self] in - self?.prepareEnviroment(animationType: animationType) - } - } else { - AppKeychainWrapper.removeBackupPasswordFromKeychain() - resetView() + + mainView.onRestoreWalletButtonTap = { [weak self] in + self?.moveToRestoreWalletScreen() } } - private func resetView() { - videoView.isHidden = false - titleLabel.isHidden = false - createWalletButton.isHidden = false - selectNetworkButton.isHidden = false - disclaimerText.isHidden = false - restoreButton.isHidden = false - Tracker.shared.track("/onboarding/introduction", "Onboarding - Introduction") - } - - private func startWalletIfNeeded() { - guard TariLib.shared.walletState == .notReady else { return } - TariLib.shared.startWallet(seedWords: nil) - } - - private func waitForWalletStart(onComplete: @escaping () -> Void, onError: ((WalletError) -> Void)?) { - - var cancel: AnyCancellable? - - cancel = TariLib.shared.walletStatePublisher - .receive(on: RunLoop.main) - .sink { walletState in - switch walletState { - case .started: - cancel?.cancel() - onComplete() - case let .startFailed(error): - cancel?.cancel() - onError?(error) - case .notReady, .starting: - break - } - } - - cancel?.store(in: &cancelables) - } - - private func createWalletBackup() { - if ICloudBackup.shared.iCloudBackupsIsOn && !ICloudBackup.shared.isValidBackupExists() { - do { - let password = AppKeychainWrapper.loadBackupPasswordFromKeychain() - try ICloudBackup.shared.createWalletBackup(password: password) - } catch { - var title = localized("iCloud_backup.error.title.create_backup") - - if let localizedError = error as? LocalizedError, localizedError.failureReason != nil { - title = localizedError.failureReason! - } - PopUpPresenter.show(message: MessageModel(title: title, message: error.localizedDescription, type: .error)) - } - } - } - - private func createNewWallet() { - do { - waitForWalletStart { - [weak self] in - guard let self = self else { return } - Tracker.shared.track( - "/onboarding/create_wallet", - "Onboarding - Create Wallet" - ) - if let _ = self.ticketTopLayoutConstraint { - self.topAnimationAndRemoveVideoAnimation { [weak self] () in - self?.navigateToHome() - } - } else { - self.navigateToHome() - } - } onError: { [weak self] _ in - guard let self = self else { return } - PopUpPresenter.show(message: MessageModel(title: localized("wallet.error.title"), message: localized("wallet.error.create_new_wallet"), type: .error)) - self.createWalletButton.variation = .normal + private func showNetworkListPopup() { + + let headerSection = PopUpHeaderWithSubtitle() + + headerSection.titleLabel.text = localized("splash.action_sheet.select_network.title") + headerSection.subtitleLabel.text = localized("splash.action_sheet.select_network.description") + + var buttonsModels = model.allNetworkNames + .enumerated() + .map { [weak self] index, networkName in + PopUpDialogButtonModel(title: networkName, type: .normal, callback: { self?.model.selectNetwork(onIndex: index) }) } - try TariLib.shared.createNewWallet(seedWords: nil) - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("wallet.error.title"), message: localized("wallet.error.create_new_wallet"), type: .error)) - createWalletButton.variation = .normal - } - } - - @objc func onCreateWalletTap() { - - createWalletButton.variation = .loading - - guard !TariLib.shared.isWalletExist else { - startOnboardingFlow(animationType: .scaleDownLogo) - return - } - - onTorSuccess { - self.createNewWallet() - } - } - - @objc func onSelectNetworkButtonTap() { - - let controller = UIAlertController(title: localized("splash.action_sheet.select_network.title"), message: localized("splash.action_sheet.select_network.description"), preferredStyle: .actionSheet) - - TariNetwork.all.forEach { [weak controller] network in - controller?.addAction(UIAlertAction(title: network.presentedName, style: .default, handler: { _ in - NetworkManager.shared.selectedNetwork = network - })) - } - - controller.addAction(UIAlertAction(title: localized("common.cancel"), style: .destructive, handler: nil)) - - present(controller, animated: true) - } - - @objc func onRestoreWalletTap() { - let restoreWalletViewController = RestoreWalletViewController() - navigationController?.pushViewController(restoreWalletViewController, animated: true) - } - - func startAnimation(onComplete: (() -> Void)? = nil) { - animationContainer.play { _ in onComplete?() } + + buttonsModels.append(PopUpDialogButtonModel(title: localized("common.cancel"), type: .text)) + + let buttonsSection = PopUpComponentsFactory.makeButtonsView(models: buttonsModels) + + let popUp = TariPopUp(headerSection: headerSection, contentSection: nil, buttonsSection: buttonsSection) + + PopUpPresenter.show(popUp: popUp) } - - private func navigateToHome() { - - switch TariSettings.shared.walletSettings.configationState { + + // MARK: - Actions + + private func moveToNextScreen() { + switch TariSettings.shared.walletSettings.configurationState { case .notConfigured: - moveToOnboarding(startFromLocalAuth: false) + moveToOnboardingScreen(startFromLocalAuth: false) case .initialized: - moveToOnboarding(startFromLocalAuth: true) + moveToOnboardingScreen(startFromLocalAuth: true) case .authorized, .ready: - moveToWallet() + moveToHomeScreen() } - - TariEventBus.unregister(self) } - - private func moveToWallet() { - NotificationManager.shared.requestAuthorization() - - let nav = AlwaysPoppableNavigationController() - let tabBarController = MenuTabBarController() - nav.setViewControllers([tabBarController], animated: false) - - if let window = UIApplication.shared.keyWindow { - let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false) - tabBarController.view.addSubview(overlayView) - window.rootViewController = nav - - UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve, animations: { - overlayView.alpha = 0 - }, completion: { _ in - overlayView.removeFromSuperview() - }) - } + + private func moveToRestoreWalletScreen() { + navigationController?.pushViewController(RestoreWalletViewController(), animated: true) } - - private func moveToOnboarding(startFromLocalAuth: Bool) { - let vc = WalletCreationViewController() - vc.startFromLocalAuth = startFromLocalAuth - if let window = view.window { - let transition: CATransition = CATransition() - transition.duration = 0.5 - transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) - transition.type = CATransitionType.push - transition.subtype = CATransitionSubtype.fromTop - - window.layer.add(Theme.shared.transitions.pullDownOpen, forKey: kCATransition) - navigationController?.view.layer.add(transition, forKey: kCATransition) - navigationController?.pushViewController(vc, animated: false) + + private func moveToOnboardingScreen(startFromLocalAuth: Bool) { + mainView.playLogoAnimation { + AppRouter.transitionToOnboardingScreen(startFromLocalAuth: startFromLocalAuth) } } - - private func update(network: TariNetwork) { - versionLabel.text = AppVersionFormatter.version - selectNetworkButton.setTitle(localized("splash.button.select_network", arguments: network.presentedName), for: .normal) - updateCreateWalletButtonState() + + private func moveToHomeScreen() { + authenticationContext.authenticateUser { [weak self] in + self?.mainView.playLogoAnimation { AppRouter.transitionToHomeScreen() } + } } - private func updateCreateWalletButtonState() { - let createWalletButtonTitle = TariLib.shared.isWalletExist ? localized("splash.button.open_wallet") : localized("splash.button.create_wallet") - createWalletButton.setTitle(createWalletButtonTitle, for: .normal) + // MARK: - Helpers + + private func handle(status: SplashViewModel.StatusModel) { + + switch (status.status, status.statusRepresentation) { + case (.idle, .content): + mainView.isCreateWalletButtonSpinnerVisible = false + mainView.updateLayout(showInterface: true, animated: animateTransitions) + case (.idle, .logo): + mainView.updateLayout(showInterface: false, animated: animateTransitions) + model.startWallet() + case (.working, .content): + mainView.isCreateWalletButtonSpinnerVisible = true + case (.working, .logo): + mainView.updateLayout(showInterface: false, animated: animateTransitions) + case (.success, .content): + mainView.updateLayout(showInterface: false, animated: animateTransitions) { [weak self] in + self?.moveToNextScreen() + } + case (.success, .logo): + moveToNextScreen() + } + + guard !animateTransitions else { return } + animateTransitions = true } } diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewControllerSetup.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewControllerSetup.swift deleted file mode 100644 index 33343ce6..00000000 --- a/MobileWallet/Screens/AppEntry/Splash/SplashViewControllerSetup.swift +++ /dev/null @@ -1,307 +0,0 @@ -// SplashViewControllerSetup.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/03/03 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import UIKit -import AVFoundation -import Lottie - -extension SplashViewController { - func setupView() { - view.addSubview(generalContainer) - generalContainer.translatesAutoresizingMaskIntoConstraints = false - - if UIDevice.current.userInterfaceIdiom == .pad { - generalContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85).isActive = true - generalContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.85).isActive = true - generalContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true - generalContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true - } else { - generalContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true - generalContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true - generalContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true - generalContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true - } - - setupAnimationContainer() - setupVideoView() - setupContraintsVersionLabel() - setupElementsContainer() - } - - func loadAnimation() { - let animation = Animation.named(.splash) - animationContainer.backgroundBehavior = .stop - animationContainer.animation = animation - } - - func setupVideoAnimation() { - videoView.url = Bundle.main.url(forResource: "purple_orb", withExtension: "mp4") - } - - func setupAnimationContainer() { - generalContainer.addSubview(animationContainer) - animationContainer.translatesAutoresizingMaskIntoConstraints = false - animationContainer.widthAnchor.constraint(equalToConstant: 145).isActive = true - animationContainer.heightAnchor.constraint(equalToConstant: 30).isActive = true - animationContainer.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - - if TariLib.shared.isWalletExist { - animationContainer.centerYAnchor.constraint(equalTo: generalContainer.centerYAnchor).isActive = true - } else { - ticketTopLayoutConstraint = animationContainer.topAnchor.constraint(equalTo: generalContainer.topAnchor, constant: 19) - ticketTopLayoutConstraint?.isActive = true - } - } - - func setupVideoView() { - videoView.isHidden = true - generalContainer.insertSubview(videoView, belowSubview: animationContainer) - videoView.translatesAutoresizingMaskIntoConstraints = false - if TariLib.shared.isWalletExist { - animationContainerBottomAnchor?.isActive = false - } else { - videoView.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - animationContainerBottomAnchor = videoView.topAnchor.constraint(equalTo: animationContainer.bottomAnchor) - animationContainerBottomAnchor?.isActive = true - videoView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true - videoView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true - } - } - - func setupElementsContainer() { - generalContainer.addSubview(elementsContainer) - - setupTitleLabel() - setupCreateWalletButton() - setupSelectNetworkButton() - setupRestoreButton() - setupDisclaimer() - setupGemImageView() - - elementsContainer.translatesAutoresizingMaskIntoConstraints = false - - elementsContainer.widthAnchor.constraint(equalTo: generalContainer.widthAnchor).isActive = true - elementsContainer.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - elementsContainer.topAnchor.constraint(equalTo: videoView.bottomAnchor, constant: -15).isActive = true - elementsContainer.bottomAnchor.constraint(equalTo: versionLabel.topAnchor, constant: -9).isActive = true - elementsContainer.bottomAnchor.constraint(equalTo: gemImageView.bottomAnchor).isActive = true - } - - func setupTitleLabel() { - titleLabel.isHidden = true - titleLabel.text = localized("splash.title") - titleLabel.interlineSpacing(spacingValue: 0) - titleLabel.numberOfLines = 2 - titleLabel.adjustsFontSizeToFitWidth = true - titleLabel.lineBreakMode = .byTruncatingTail - titleLabel.font = Theme.shared.fonts.splashTitleLabel - titleLabel.textColor = Theme.shared.colors.splashTitle - - elementsContainer.addSubview(titleLabel) - - titleLabel.textAlignment = .center - titleLabel.translatesAutoresizingMaskIntoConstraints = false - titleLabel.leadingAnchor.constraint(equalTo: generalContainer.leadingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true - titleLabel.trailingAnchor.constraint(equalTo: generalContainer.trailingAnchor, constant: -Theme.shared.sizes.appSidePadding).isActive = true - titleLabel.topAnchor.constraint(equalTo: elementsContainer.topAnchor).isActive = true - } - - func setupCreateWalletButton() { - createWalletButton.isHidden = true - createWalletButton.addTarget(self, action: #selector(onCreateWalletTap), for: .touchUpInside) - elementsContainer.addSubview(createWalletButton) - - createWalletButton.translatesAutoresizingMaskIntoConstraints = false - - createWalletButton.leadingAnchor.constraint(equalTo: generalContainer.leadingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true - createWalletButton.trailingAnchor.constraint(equalTo: generalContainer.trailingAnchor, constant: -Theme.shared.sizes.appSidePadding).isActive = true - createWalletButton.topAnchor.constraint(greaterThanOrEqualTo: titleLabel.bottomAnchor, constant: 5).isActive = true - createWalletButton.topAnchor.constraint(lessThanOrEqualTo: titleLabel.bottomAnchor, constant: 25).isActive = true - } - - func setupSelectNetworkButton() { - - selectNetworkButton.translatesAutoresizingMaskIntoConstraints = false - selectNetworkButton.isHidden = true - selectNetworkButton.addTarget(self, action: #selector(onSelectNetworkButtonTap), for: .touchUpInside) - - elementsContainer.addSubview(selectNetworkButton) - - let constraints = [ - selectNetworkButton.leadingAnchor.constraint(equalTo: generalContainer.leadingAnchor, constant: Theme.shared.sizes.appSidePadding), - selectNetworkButton.trailingAnchor.constraint(equalTo: generalContainer.trailingAnchor, constant: -Theme.shared.sizes.appSidePadding), - selectNetworkButton.topAnchor.constraint(equalTo: createWalletButton.bottomAnchor, constant: 12.0), - selectNetworkButton.heightAnchor.constraint(equalToConstant: 32.0) - ] - - NSLayoutConstraint.activate(constraints) - } - - func setupRestoreButton() { - restoreButton.isHidden = true - restoreButton.backgroundColor = .clear - restoreButton.tintColor = .white - restoreButton.titleLabel?.font = Theme.shared.fonts.restoreWalletButton - restoreButton.addTarget(self, action: #selector(onRestoreWalletTap), for: .touchUpInside) - let title = localized("splash.restore_wallet") - restoreButton.setTitle(title, for: .normal) - - elementsContainer.addSubview(restoreButton) - restoreButton.translatesAutoresizingMaskIntoConstraints = false - restoreButton.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - restoreButton.topAnchor.constraint(greaterThanOrEqualTo: selectNetworkButton.bottomAnchor, constant: 5.0).isActive = true - restoreButton.topAnchor.constraint(lessThanOrEqualTo: selectNetworkButton.bottomAnchor, constant: 22.0).isActive = true - restoreButton.heightAnchor.constraint(equalToConstant: 25).isActive = true - } - - func setupDisclaimer() { - disclaimerText.isHidden = true - elementsContainer.addSubview(disclaimerText) - disclaimerText.translatesAutoresizingMaskIntoConstraints = false - disclaimerText.isEditable = false - - let userAgreementLinkText = localized("splash.disclaimer.param.user_agreement") - let privacyPolicyLinkText = localized("splash.disclaimer.param.privacy_policy") - let text = String( - format: localized("splash.disclaimer.with_params"), - userAgreementLinkText, - privacyPolicyLinkText) - - let attributedText = NSMutableAttributedString(string: text) - - disclaimerText.linkTextAttributes = [ - NSAttributedString.Key.foregroundColor: Theme.shared.colors.splashVersionLabel!, - NSAttributedString.Key.underlineColor: Theme.shared.colors.splashVersionLabel!, - NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue - ] - - if let userAgreementStartIndex = text.indexDistance(of: userAgreementLinkText) { - let range = NSRange(location: userAgreementStartIndex, length: userAgreementLinkText.count) - attributedText.addAttribute(.link, value: TariSettings.shared.userAgreementUrl, range: range) - attributedText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) - attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: range) - } - - if let privacyPolicyStartIndex = text.indexDistance(of: privacyPolicyLinkText) { - let range = NSRange(location: privacyPolicyStartIndex, length: privacyPolicyLinkText.count) - attributedText.addAttribute(.link, value: TariSettings.shared.privacyPolicyUrl, range: range) - attributedText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) - } - - disclaimerText.attributedText = attributedText - - disclaimerText.backgroundColor = .clear - disclaimerText.textColor = Theme.shared.colors.splashVersionLabel - disclaimerText.font = Theme.shared.fonts.splashDisclaimerLabel - disclaimerText.textAlignment = .center - disclaimerText.isScrollEnabled = false - - disclaimerText.leadingAnchor.constraint(equalTo: generalContainer.leadingAnchor, constant: Theme.shared.sizes.appSidePadding).isActive = true - disclaimerText.trailingAnchor.constraint(equalTo: generalContainer.trailingAnchor, constant: -Theme.shared.sizes.appSidePadding).isActive = true - disclaimerText.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - disclaimerText.topAnchor.constraint(greaterThanOrEqualTo: restoreButton.bottomAnchor, constant: 0).isActive = true - disclaimerText.topAnchor.constraint(lessThanOrEqualTo: restoreButton.bottomAnchor, constant: 5).isActive = true - } - - func setupGemImageView() { - elementsContainer.addSubview(gemImageView) - gemImageView.image = Theme.shared.images.currencySymbol - gemImageView.translatesAutoresizingMaskIntoConstraints = false - gemImageView.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor).isActive = true - gemImageView.topAnchor.constraint(greaterThanOrEqualTo: disclaimerText.bottomAnchor, constant: 0).isActive = true - gemImageView.topAnchor.constraint(lessThanOrEqualTo: disclaimerText.bottomAnchor, constant: 12).isActive = true - gemImageView.widthAnchor.constraint(equalToConstant: 24).isActive = true - gemImageView.heightAnchor.constraint(equalToConstant: 24).isActive = true - } - - func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - UIApplication.shared.open(URL) - return false - } - - func setupContraintsVersionLabel() { - versionLabel.font = Theme.shared.fonts.splashVersionFooterLabel - versionLabel.textColor = Theme.shared.colors.splashVersionLabel - - generalContainer.addSubview(versionLabel) - versionLabel.textAlignment = .center - versionLabel.numberOfLines = 0 - versionLabel.translatesAutoresizingMaskIntoConstraints = false - - versionLabel.bottomAnchor.constraint(equalTo: generalContainer.bottomAnchor, - constant: -12).isActive = true - versionLabel.leadingAnchor.constraint(equalTo: generalContainer.leadingAnchor, - constant: Theme.shared.sizes.appSidePadding).isActive = true - versionLabel.trailingAnchor.constraint(equalTo: generalContainer.trailingAnchor, - constant: -Theme.shared.sizes.appSidePadding).isActive = true - versionLabel.centerXAnchor.constraint(equalTo: generalContainer.centerXAnchor, - constant: 0).isActive = true - } - - func titleAnimation() { - UIView.animate(withDuration: 0.5) { [weak self] in - guard let self = self else { return } - self.distanceTitleSubtitle.constant = 30.0 - self.view.layoutIfNeeded() - } - } - - func topAnimationAndRemoveVideoAnimation(onComplete: @escaping () -> Void) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.ticketTopLayoutConstraint?.isActive = false - self.animationContainerBottomAnchor?.isActive = false - self.animationContainerBottomAnchorToVideo?.isActive = false - self.ticketBottom?.isActive = false - self.videoView.isHidden = true - self.animationContainer.bottomAnchor.constraint(equalTo: self.generalContainer.centerYAnchor).isActive = true - - UIView.animate(withDuration: 1.0, animations: { [weak self] in - guard let self = self else { return } - self.elementsContainer.alpha = 0 - self.versionLabel.alpha = 0 - self.view.layoutIfNeeded() - }) { [weak self] (_) in - guard let self = self else { return } - self.startAnimation(onComplete: onComplete) - } - } - } -} diff --git a/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift new file mode 100644 index 00000000..00fc5f7a --- /dev/null +++ b/MobileWallet/Screens/AppEntry/Splash/SplashViewModel.swift @@ -0,0 +1,179 @@ +// SplashViewModel.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 21/07/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class SplashViewModel { + + private enum InternalError: Error { + case disconnectedFromTor + } + + enum Status { + case idle + case working + case success + } + + enum StatusRepresentation { + case content + case logo + } + + struct StatusModel { + let status: Status + let statusRepresentation: StatusRepresentation + } + + // MARK: - View Model + + @Published private(set) var status: StatusModel? + @Published private(set) var networkName: String? + @Published private(set) var appVersion: String? + @Published private(set) var allNetworkNames: [String] = [] + @Published private(set) var isWalletExist: Bool = false + @Published private(set) var errorMessage: MessageModel? + + // MARK: - Properties + + private var cancellables = Set() + + // MARK: - Initialisers + + init() { + status = StatusModel(status: .idle, statusRepresentation: Tari.shared.isWalletExist ? .logo : .content) + setupCallbacks() + setupData() + } + + // MARK: - Setups + + private func setupCallbacks() { + + NetworkManager.shared.$selectedNetwork + .map(\.presentedName) + .sink { [weak self] in self?.networkName = $0 } + .store(in: &cancellables) + + NetworkManager.shared.$selectedNetwork + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in self?.isWalletExist = Tari.shared.isWalletExist } + .store(in: &cancellables) + + $isWalletExist + .dropFirst() + .filter { !$0 } + .sink { _ in AppKeychainWrapper.removeBackupPasswordFromKeychain() } + .store(in: &cancellables) + } + + private func setupData() { + appVersion = AppVersionFormatter.version + allNetworkNames = TariNetwork.all.map { $0.presentedName } + } + + // MARK: - View Model Actions + + func selectNetwork(onIndex index: Int) { + NetworkManager.shared.selectedNetwork = TariNetwork.all[index] + } + + func startWallet() { + Tari.shared.isWalletExist ? openWallet() : createWallet() + } + + // MARK: - Actions + + private func createWallet() { + Task { + do { + status = StatusModel(status: .working, statusRepresentation: .content) + try await connectToWallet() + status = StatusModel(status: .success, statusRepresentation: .content) + } catch { + handle(error: error) + } + } + } + + private func openWallet() { + Task { + do { + let statusRepresentation = status?.statusRepresentation ?? .content + status = StatusModel(status: .working, statusRepresentation: statusRepresentation) + try await connectToWallet() + status = StatusModel(status: .success, statusRepresentation: statusRepresentation) + } catch { + self.handle(error: error) + } + } + } + + private func connectToWallet() async throws { + try await Tari.shared.startWallet() + try Tari.shared.keyValues.set(key: .network, value: NetworkManager.shared.selectedNetwork.name) + Tari.shared.canAutomaticalyReconnectWallet = true + } + + private func handle(completion: Subscribers.Completion) -> Error? { + switch completion { + case .finished: + return nil + case let .failure(error): + return error + } + } + + private func handle(error: Error) { + + status = StatusModel(status: .idle, statusRepresentation: .content) + + guard let error = error as? InternalError else { + let message = ErrorMessageManager.errorMessage(forError: error) + errorMessage = MessageModel(title: localized("splash.wallet_error.title"), message: message, type: .error) + return + } + + switch error { + case .disconnectedFromTor: + errorMessage = MessageModel(title: localized("tor.error.title"), message: localized("tor.error.description"), type: .error) + } + } +} diff --git a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift index 03a26468..d7a63b1f 100644 --- a/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift +++ b/MobileWallet/Screens/AppEntry/Splash/WalletCreationViewController.swift @@ -43,7 +43,7 @@ import Lottie import LocalAuthentication import AVFoundation -class WalletCreationViewController: UIViewController { +final class WalletCreationViewController: UIViewController { typealias LottieAnimation = Animation.LottieAnimation // MARK: - States @@ -108,7 +108,6 @@ class WalletCreationViewController: UIViewController { state = .localAuthentication prepareSubviews(for: .localAuthentication) showLocalAuthentication() - Tracker.shared.track("/local_auth", "Local Authentication") } // MARK: - Actions @@ -196,10 +195,9 @@ class WalletCreationViewController: UIViewController { self?.updateConstraintsAnimationView(animation: .none) self?.showYourEmoji() }) - Tracker.shared.track("/onboarding/create_emoji_id", "Onboarding - Create Emoji Id") } case .showEmojiId: - TariSettings.shared.walletSettings.configationState = .initialized + TariSettings.shared.walletSettings.configurationState = .initialized hideSubviews { [weak self] in self?.emojiIdView.shrink(animated: false) self?.prepareSubviews(for: .localAuthentication) @@ -216,23 +214,7 @@ class WalletCreationViewController: UIViewController { private func runNotificationRequest() { NotificationManager.shared.requestAuthorization { _ in DispatchQueue.main.async { - Tracker.shared.track("/onboarding/enable_push_notif", "Onboarding - Enable Push Notifications") - - let nav = AlwaysPoppableNavigationController() - let tabBarController = MenuTabBarController() - nav.setViewControllers([tabBarController], animated: false) - - if let window = UIApplication.shared.keyWindow { - let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false) - tabBarController.view.addSubview(overlayView) - window.rootViewController = nav - - UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve, animations: { - overlayView.alpha = 0 - }, completion: { _ in - overlayView.removeFromSuperview() - }) - } + AppRouter.transitionToHomeScreen() } } } @@ -242,7 +224,7 @@ class WalletCreationViewController: UIViewController { } private func successAuth() { - TariSettings.shared.walletSettings.configationState = .authorized + TariSettings.shared.walletSettings.configurationState = .authorized hideSubviews { [weak self] in self?.prepareSubviews(for: .enableNotifications) self?.showEnableNotifications() @@ -421,10 +403,11 @@ extension WalletCreationViewController { stackView.setCustomSpacing(16, after: secondLabel) continueButton.setTitle(localized("common.continue"), for: .normal) - - if let pubKey = TariLib.shared.tariWallet?.publicKey.0 { - emojiIdView.setupView( - pubKey: pubKey, + + if let walletPublicKey = try? Tari.shared.walletPublicKey, let emojiID = try? walletPublicKey.emojis, let hex = try? walletPublicKey.byteVector.hex { + emojiIdView.setup( + emojiID: emojiID, + hex: hex, textCentered: true, inViewController: self, showContainerViewBlur: false diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift b/MobileWallet/Screens/Debug/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift index b422f433..83a63dd1 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/AddBaseNode/AddBaseNodeModel.swift @@ -61,7 +61,7 @@ final class AddBaseNodeModel { } do { - try BaseNodeManager.addBaseNode(name: viewModel.name, peer: viewModel.peer) + try Tari.shared.connection.addBaseNode(name: viewModel.name, peer: viewModel.peer) viewModel.isFinished = true viewModel.errorMessage = nil } catch { diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/BridgesConfigurationViewController.swift b/MobileWallet/Screens/Debug/AdvancedSettings/BridgesConfigurationViewController.swift index f7433386..39591864 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/BridgesConfigurationViewController.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/BridgesConfigurationViewController.swift @@ -39,18 +39,21 @@ */ import UIKit +import Combine -class BridgesConfigurationViewController: SettingsParentTableViewController { +final class BridgesConfigurationViewController: SettingsParentTableViewController, CustomBridgesHandable { typealias BridgesType = OnionSettings.BridgesType - private lazy var bridgesConfiguration: BridgesConfuguration = { + private lazy var bridgesConfiguration: BridgesConfiguration = { OnionSettings.currentlyUsedBridgesConfiguration }() private enum Section: Int { case chooseBridge = 1 } + + private var cancellables = Set() private enum BridgesConfigurationItemTitle: CaseIterable { case requestBridgesFromTorproject @@ -73,8 +76,8 @@ class BridgesConfigurationViewController: SettingsParentTableViewController { private func getBridgeSectionItems() -> [SystemMenuTableViewCellItem] { [ - SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.noBridges.rawValue, mark: OnionConnector.shared.bridgesConfiguration.bridgesType == BridgesType.none ? .scheduled : .none, hasArrow: false), - SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.custom.rawValue, mark: OnionConnector.shared.bridgesConfiguration.bridgesType == BridgesType.custom ? .scheduled : .none) + SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.noBridges.rawValue, mark: Tari.shared.torBridgesConfiguration.bridgesType == BridgesType.none ? .scheduled : .none, hasArrow: false), + SystemMenuTableViewCellItem(title: BridgesConfigurationItemTitle.custom.rawValue, mark: Tari.shared.torBridgesConfiguration.bridgesType == BridgesType.custom ? .scheduled : .none) ] } @@ -82,6 +85,9 @@ class BridgesConfigurationViewController: SettingsParentTableViewController { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self + + setupCustomBridgeProgressHandler() + .store(in: &cancellables) } override func viewWillAppear(_ animated: Bool) { @@ -98,12 +104,19 @@ extension BridgesConfigurationViewController { navigationBar.rightButton.isEnabled = false navigationBar.rightButtonAction = { [weak self] in guard let self = self else { return } - OnionConnector.shared.addObserver(self) self.navigationBar.setProgress(0.0) self.navigationBar.progressView.isHidden = false self.navigationBar.rightButton.isEnabled = false self.view.isUserInteractionEnabled = false - OnionConnector.shared.bridgesConfiguration = self.bridgesConfiguration + Task { [weak self] in + do { + guard let self = self else { return } + try await Tari.shared.update(torBridgesConfiguration: self.bridgesConfiguration) + self.onCustomBridgeSuccessAction() + } catch { + self?.onCustomBridgeFailureAction(error: error) + } + } } let title = localized("bridges_configuration.connect") diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/CustomBridgesViewController.swift b/MobileWallet/Screens/Debug/AdvancedSettings/CustomBridgesViewController.swift index cdb77515..88eafdcb 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/CustomBridgesViewController.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/CustomBridgesViewController.swift @@ -39,8 +39,9 @@ */ import UIKit +import Combine -class CustomBridgesViewController: SettingsParentTableViewController { +final class CustomBridgesViewController: SettingsParentTableViewController, CustomBridgesHandable { private enum Section: Int, CaseIterable { case requestBridges @@ -61,7 +62,7 @@ class CustomBridgesViewController: SettingsParentTableViewController { } } - private weak var bridgesConfiguration: BridgesConfuguration? + private weak var bridgesConfiguration: BridgesConfiguration? private let examplePlaceHolderString = "Available formates:\nโ€ข obfs4 : cert= iat-mode=\nexample:\nobfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1\n\n โ€ข : \nexample:\n78.156.103.189:9301 2BD90810282F8B331FC7D47705167166253E1442" private let customBridgeField = UITextView() private lazy var detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) @@ -74,8 +75,10 @@ class CustomBridgesViewController: SettingsParentTableViewController { SystemMenuTableViewCellItem(title: CustomBridgesTitle.scanQRCode.rawValue), SystemMenuTableViewCellItem(title: CustomBridgesTitle.uploadQRCode.rawValue) ] + + private var cancellables = Set() - init(bridgesConfiguration: BridgesConfuguration) { + init(bridgesConfiguration: BridgesConfiguration) { self.bridgesConfiguration = bridgesConfiguration super.init(nibName: nil, bundle: nil) } @@ -88,6 +91,9 @@ class CustomBridgesViewController: SettingsParentTableViewController { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self + + setupCustomBridgeProgressHandler() + .store(in: &cancellables) } } @@ -106,14 +112,14 @@ extension CustomBridgesViewController { navigationBar.rightButton.titleLabel?.font = Theme.shared.fonts.settingsDoneButton } - override func onTorConnDifficulties() { - super.onTorConnDifficulties() + private func onTorConnDifficulties(error: Error) { applyConnectingStatus() // for returning to previous bridges configuration let backupConfiguration = OnionSettings.backupBridgesConfiguration let currentBridgesStr = backupConfiguration.customBridges?.joined(separator: "\n") bridgesConfiguration?.customBridges = backupConfiguration.customBridges bridgesConfiguration?.bridgesType = backupConfiguration.bridgesType customBridgeField.text = (currentBridgesStr?.isEmpty ?? true) ? examplePlaceHolderString : currentBridgesStr + onCustomBridgeFailureAction(error: error) } private func openScannerVC() { @@ -131,21 +137,30 @@ extension CustomBridgesViewController { } private func connectAction() { - if bridgesConfiguration == nil { return } + + guard let bridgesConfiguration = bridgesConfiguration else { return } applyConnectingStatus() - bridgesConfiguration?.customBridges = customBridgeField.text + bridgesConfiguration.customBridges = customBridgeField.text .components(separatedBy: "\n") .map({ bridge in bridge.trimmingCharacters(in: .whitespacesAndNewlines) }) .filter({ bridge in !bridge.isEmpty && !bridge.hasPrefix("//") && !bridge.hasPrefix("#") }) - bridgesConfiguration?.bridgesType = bridgesConfiguration?.customBridges?.isEmpty == true ? .none : .custom - OnionConnector.shared.bridgesConfiguration = bridgesConfiguration! + bridgesConfiguration.bridgesType = bridgesConfiguration.customBridges?.isEmpty == true ? .none : .custom + customBridgeField.resignFirstResponder() + + Task { + do { + try await Tari.shared.update(torBridgesConfiguration: bridgesConfiguration) + onCustomBridgeSuccessAction() + } catch { + onTorConnDifficulties(error: error) + } + } } private func applyConnectingStatus() { - OnionConnector.shared.addObserver(self) navigationBar.setProgress(0.0) navigationBar.progressView.isHidden = false navigationBar.rightButton.isEnabled = false diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/Select Network/SelectNetworkModel.swift b/MobileWallet/Screens/Debug/AdvancedSettings/Select Network/SelectNetworkModel.swift index 55f66dd5..4efb13ae 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/Select Network/SelectNetworkModel.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/Select Network/SelectNetworkModel.swift @@ -66,8 +66,8 @@ final class SelectNetworkModel { func update(selectedIndex: Int) { guard viewModel.selectedIndex != selectedIndex else { return } - TariLib.shared.stopWallet() - NetworkManager.shared.selectedNetwork = networks[selectedIndex] - AppRouter.moveToSplashScreen() + Tari.shared.select(network: networks[selectedIndex]) + Tari.shared.canAutomaticalyReconnectWallet = false + AppRouter.transitionToSplashScreen() } } diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift b/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift index cedcb792..e1206221 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeModel.swift @@ -46,15 +46,14 @@ final class SelectBaseNodeModel { let isSelected: Bool let canBeRemoved: Bool } + + // MARK: - View Model - final class ViewModel { - @Published var nodes: [NodeModel] = [] - } + @Published var nodes: [NodeModel] = [] + @Published var errorMessaage: MessageModel? // MARK: - Properties - var viewModel = ViewModel() - private var predefinedNodes: [BaseNode] { NetworkManager.shared.selectedNetwork.baseNodes } private var avaiableNodes: [BaseNode] { NetworkManager.shared.selectedNetwork.allBaseNodes } private var selectedNodeIndex: Int? @@ -67,7 +66,7 @@ final class SelectBaseNodeModel { } private func updateViewModelNodes() { - viewModel.nodes = avaiableNodes + nodes = avaiableNodes .enumerated() .map { let isSelected = $0 == selectedNodeIndex @@ -90,9 +89,13 @@ final class SelectBaseNodeModel { func selectNode(index: Int) { let baseNode = avaiableNodes[index] - try? TariLib.shared.update(baseNode: baseNode, syncAfterSetting: true) - selectedNodeIndex = index - updateViewModelNodes() + do { + try Tari.shared.connection.select(baseNode: baseNode) + selectedNodeIndex = index + updateViewModelNodes() + } catch { + errorMessaage = ErrorMessageManager.errorModel(forError: error) + } } func deleteNode(index: Int) { diff --git a/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift b/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift index 84de841f..2e19165c 100644 --- a/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift +++ b/MobileWallet/Screens/Debug/AdvancedSettings/SelectBaseNode/SelectBaseNodeViewController.swift @@ -47,13 +47,13 @@ final class SelectBaseNodeViewController: SettingsParentTableViewController { private let model = SelectBaseNodeModel() private var dataSource: UITableViewDiffableDataSource? - private var cancelables: Set = [] + private var cancellables: Set = [] // MARK: - View lifecycle override func viewDidLoad() { super.viewDidLoad() - setupFeedbacks() + setupCallbacks() } @@ -81,7 +81,7 @@ final class SelectBaseNodeViewController: SettingsParentTableViewController { tableView.register(type: SelectBaseNodeCell.self) } - private func setupFeedbacks() { + private func setupCallbacks() { tableView.delegate = self @@ -108,14 +108,20 @@ final class SelectBaseNodeViewController: SettingsParentTableViewController { tableView.dataSource = dataSource - model.viewModel.$nodes + model.$nodes .sink { [weak self] models in var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(models, toSection: 0) self?.dataSource?.apply(snapshot, animatingDifferences: false) } - .store(in: &cancelables) + .store(in: &cancellables) + + model.$errorMessaage + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { PopUpPresenter.show(message: $0) } + .store(in: &cancellables) } // MARK: - Actions diff --git a/MobileWallet/Screens/Debug/DebugLogsTableViewController.swift b/MobileWallet/Screens/Debug/DebugLogsTableViewController.swift index 6826eb2c..53a2048f 100644 --- a/MobileWallet/Screens/Debug/DebugLogsTableViewController.swift +++ b/MobileWallet/Screens/Debug/DebugLogsTableViewController.swift @@ -78,6 +78,10 @@ class DebugLogsTableViewController: UITableViewController { } } } + + private var logsURLs: [URL] { + (try? Tari.shared.logsURLs) ?? [] + } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -219,7 +223,7 @@ class DebugLogsTableViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return currentLogFile != nil ? logLines.count : TariLib.shared.allLogFiles.count + return currentLogFile != nil ? logLines.count : logsURLs.count } override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { @@ -237,10 +241,10 @@ class DebugLogsTableViewController: UITableViewController { cell.textLabel?.text = logLines[indexPath.row] cell.textLabel?.numberOfLines = 10 } else { - let url = TariLib.shared.allLogFiles[indexPath.row] + let url = logsURLs[indexPath.row] let filename = url.lastPathComponent - var labelText = TariLib.shared.logFilePath.contains(filename) ? "\(filename) (current)" : filename + var labelText = Tari.shared.logFilePath.contains(filename) ? "\(filename) (current)" : filename do { let attr = try FileManager.default.attributesOfItem(atPath: url.path) @@ -250,7 +254,7 @@ class DebugLogsTableViewController: UITableViewController { labelText = "\(labelText) - \(formattedSize)" } catch { - TariLogger.error("Failed to get log file size", error: error) + Logger.log(message: "Failed to get log file size: \(error.localizedDescription)", domain: .general, level: .error) } cell.textLabel?.text = labelText @@ -268,7 +272,7 @@ class DebugLogsTableViewController: UITableViewController { } let logsVC = DebugLogsTableViewController() - logsVC.currentLogFile = TariLib.shared.allLogFiles[indexPath.row] + logsVC.currentLogFile = logsURLs[indexPath.row] navigationController?.pushViewController(logsVC, animated: true) } } diff --git a/MobileWallet/Screens/Debug/UIViewControllerDebugExtension.swift b/MobileWallet/Screens/Debug/UIViewControllerDebugExtension.swift index e53dba93..eb73c132 100644 --- a/MobileWallet/Screens/Debug/UIViewControllerDebugExtension.swift +++ b/MobileWallet/Screens/Debug/UIViewControllerDebugExtension.swift @@ -87,7 +87,7 @@ extension UIViewController: MFMailComposeViewControllerDelegate { footer.append("
\(key): \(value)") } footer.append("
") - footer.append(LegacyConnectionMonitor.shared.state.formattedDisplayItems.joined(separator: "
")) + footer.append(Tari.shared.connectionMonitor.formattedDisplayItems.joined(separator: "
")) footer.append("

") return footer @@ -106,34 +106,34 @@ extension UIViewController: MFMailComposeViewControllerDelegate { let archiveName = "\(dateString)-bug-report.zip" guard let archiveURL = FileManager.default.documentDirectory()?.appendingPathComponent(archiveName) else { - TariLogger.error("Failed to create archive URL") + Logger.log(message: "Failed to create archive URL", domain: .general, level: .error) throw DebugErrors.zipURL } // ZIP db files only if this is debug // An archive needs to be created first before multipl files can be appended. // If this is mainnet then just the current log file gets created and the rest will get appended below. - var sourceURL = URL(fileURLWithPath: TariLib.shared.logFilePath) + var sourceURL = URL(fileURLWithPath: Tari.shared.logFilePath) // Only allow attaching DB files in debug and testflight if TariSettings.shared.environment != .production { - sourceURL = TariLib.shared.connectedDatabaseDirectory + sourceURL = Tari.shared.connectedDatabaseDirectory } do { try FileManager().zipItem(at: sourceURL, to: archiveURL) } catch { - TariLogger.error("Creation of ZIP archive failed with error", error: error) + Logger.log(message: "Creation of ZIP archive failed with error: \(error.localizedDescription)", domain: .general, level: .error) throw DebugErrors.createArchive } // Add log file entries guard let archive = Archive(url: archiveURL, accessMode: .update) else { - TariLogger.error("Failed to access archive") + Logger.log(message: "Failed to access archive", domain: .general, level: .error) throw DebugErrors.zipArchive } var limit = 5 - try TariLib.shared.allLogFiles.forEach { (logFile) in + try Tari.shared.logsURLs.forEach { (logFile) in guard limit > 0 else { return } @@ -169,8 +169,6 @@ extension UIViewController: MFMailComposeViewControllerDelegate { } else { shareFeedback() } - - TariLogger.info("Feedback shared") } private func shareFeedback() { @@ -283,12 +281,21 @@ extension UIViewController: MFMailComposeViewControllerDelegate { private func showConnectionStatusPopUp() { let headerSection = PopUpComponentsFactory.makeHeaderView(title: "Connection status") - let contentSection = PopUpComponentsFactory.makeContentView(message: LegacyConnectionMonitor.shared.state.formattedDisplayItems.joined(separator: "\n\n")) + let contentSection = PopUpComponentsFactory.makeContentView(message: Tari.shared.connectionMonitor.formattedDisplayItems.joined(separator: "\n\n")) - let event = TariEventBus.events(forType: .connectionMonitorStatusChanged) - .sink { [weak contentSection] _ in contentSection?.label.text = LegacyConnectionMonitor.shared.state.formattedDisplayItems.joined(separator: "\n\n") } + let events = [ + Tari.shared.connectionMonitor.$networkConnection.map { _ in Void() }.eraseToAnyPublisher(), + Tari.shared.connectionMonitor.$torConnection.map { _ in Void() }.eraseToAnyPublisher(), + Tari.shared.connectionMonitor.$torBootstrapProgress.map { _ in Void() }.eraseToAnyPublisher(), + Tari.shared.connectionMonitor.$baseNodeConnection.map { _ in Void() }.eraseToAnyPublisher(), + Tari.shared.connectionMonitor.$syncStatus.map { _ in Void() }.eraseToAnyPublisher() + ] + .map { $0 + .receive(on: DispatchQueue.main) + .sink { [weak contentSection] _ in contentSection?.label.text = Tari.shared.connectionMonitor.formattedDisplayItems.joined(separator: "\n\n") } + } - let buttonsSection = PopUpComponentsFactory.makeButtonsView(models: [PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { event.cancel() })]) + let buttonsSection = PopUpComponentsFactory.makeButtonsView(models: [PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { events.forEach { $0.cancel() } })]) let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) PopUpPresenter.show(popUp: popUp, configuration: .dialog(hapticType: .none)) diff --git a/MobileWallet/Screens/Home/Home/HomeView.swift b/MobileWallet/Screens/Home/Home/HomeView.swift index 78e23772..3d836d40 100644 --- a/MobileWallet/Screens/Home/Home/HomeView.swift +++ b/MobileWallet/Screens/Home/Home/HomeView.swift @@ -90,6 +90,7 @@ final class HomeView: UIView { let view = AnimatedBalanceLabel() view.animationSpeed = .slow view.translatesAutoresizingMaskIntoConstraints = false + view.setContentCompressionResistancePriority(.required, for: .vertical) return view }() @@ -106,21 +107,19 @@ final class HomeView: UIView { let view = AnimatedBalanceLabel() view.animationSpeed = .slow view.translatesAutoresizingMaskIntoConstraints = false + view.setContentCompressionResistancePriority(.required, for: .vertical) return view }() - let amountHelpButton: UIButton = { - let view = UIButton() + let amountHelpButton: BaseButton = { + let view = BaseButton() view.translatesAutoresizingMaskIntoConstraints = false view.setImage(UIImage(systemName: "questionmark.circle"), for: .normal) view.tintColor = .white return view }() - @View private var connectionStatusButton: BaseButton = { - let view = BaseButton() - return view - }() + @View private var connectionStatusButton: BaseButton = BaseButton() @View var utxosWalletButton: BaseButton = { let view = BaseButton() @@ -161,14 +160,13 @@ final class HomeView: UIView { private(set) var toolbarBottomConstraint: NSLayoutConstraint? private(set) var toolbarHeightConstraint: NSLayoutConstraint? - private var cancelables: Set = [] - // MARK: - Initializers init() { super.init(frame: .zero) setupLayers() setupConstraints() + setupCallbacks() } required init?(coder: NSCoder) { @@ -205,10 +203,12 @@ final class HomeView: UIView { tariIconView.centerYAnchor.constraint(equalTo: balanceValueLabel.centerYAnchor), balanceValueLabel.topAnchor.constraint(equalTo: balanceTitleLabel.bottomAnchor, constant: -7.0), balanceValueLabel.leadingAnchor.constraint(equalTo: tariIconView.trailingAnchor, constant: 8.0), + balanceValueLabel.trailingAnchor.constraint(equalTo: utxosWalletButton.leadingAnchor, constant: -8.0), avaiableFoundsTitleLabel.topAnchor.constraint(equalTo: balanceValueLabel.bottomAnchor, constant: -1.0), avaiableFoundsTitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0), avaiableFoundsValueLabel.topAnchor.constraint(equalTo: avaiableFoundsTitleLabel.bottomAnchor), avaiableFoundsValueLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0), + avaiableFoundsValueLabel.trailingAnchor.constraint(equalTo: utxosWalletButton.leadingAnchor, constant: -8.0), amountHelpButton.leadingAnchor.constraint(equalTo: avaiableFoundsTitleLabel.trailingAnchor, constant: 4.0), amountHelpButton.centerYAnchor.constraint(equalTo: avaiableFoundsTitleLabel.centerYAnchor), amountHelpButton.heightAnchor.constraint(equalToConstant: 18.0), @@ -229,11 +229,11 @@ final class HomeView: UIView { NSLayoutConstraint.activate(constraints) } - - // MARK: - Action Targets - - @objc private func onAmountHelpButtonTapAction() { - onAmountHelpButtonTap?() + + private func setupCallbacks() { + amountHelpButton.onTap = { [weak self] in + self?.onAmountHelpButtonTap?() + } } // MARK: - Layout diff --git a/MobileWallet/Screens/Home/Home/HomeViewController.swift b/MobileWallet/Screens/Home/Home/HomeViewController.swift index e1a430a7..1d914b5d 100644 --- a/MobileWallet/Screens/Home/Home/HomeViewController.swift +++ b/MobileWallet/Screens/Home/Home/HomeViewController.swift @@ -59,22 +59,15 @@ final class HomeViewController: UIViewController { private let model = HomeViewModel() private let mainView = HomeView() - private let tapOnKeyWindowGestureRecognizer = UITapGestureRecognizer() private var hapticEnabled = false private let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) private var keyServer: KeyServer? - private var selectedTx: TxProtocol? - - private var lastFPCPosition: FloatingPanel.FloatingPanelPosition = .half - - private var balanceRefreshIsWaitingForWallet = false - private var tableDataReloadIsWaitingForWallet = false - private var networkCompatibilityCheckIsWaitingForWallet = false + private var isNetworkCompatibilityChecked = false private var isFirstIntroToWallet: Bool { - TariSettings.shared.walletSettings.configationState != .ready + TariSettings.shared.walletSettings.configurationState != .ready } private var isTxViewFullScreen: Bool = false { @@ -87,7 +80,7 @@ final class HomeViewController: UIViewController { private var grabberWidthConstraint: NSLayoutConstraint? override var navBarHeight: CGFloat { (UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0.0) + 56.0 } - private var cancelables: Set = [] + private var cancellables: Set = [] private lazy var txsTableVC: TxsListViewController = { let txController = TxsListViewController() @@ -104,7 +97,6 @@ final class HomeViewController: UIViewController { return view }() - private var isGrabberVisible: Bool = true { didSet { grabberWidthConstraint?.constant = isGrabberVisible ? 55.0 : 0.0 @@ -112,6 +104,8 @@ final class HomeViewController: UIViewController { } } + override var preferredStatusBarStyle: UIStatusBarStyle { isTxViewFullScreen ? .darkContent : .lightContent } + // MARK: - View Lifecycle override func loadView() { @@ -124,7 +118,14 @@ final class HomeViewController: UIViewController { setupFloatingPanel() setupCallbacks() setupKeyServer() - Tracker.shared.track("/home", "Home - Transaction List") + NotificationManager.shared.requestAuthorization() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + ShortcutsManager.executeQueuedShortcut() + checkImportSecondUtxo() + checkIncompatibleNetwork() } // MARK: - Setups @@ -186,12 +187,22 @@ final class HomeViewController: UIViewController { .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.connectionStatusIcon = $0 } - .store(in: &cancelables) + .store(in: &cancellables) + + model.$balance + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.update(balance: $0) } + .store(in: &cancellables) + + model.$availableBalance + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.update(avaiableToSpendAmount: $0) } + .store(in: &cancellables) mainView.onOnCloseButtonTap = { [weak self] in self?.txsTableVC.tableView.scrollToTop(animated: true) self?.floatingPanelController.move(to: .half, animated: true) - self?.animateNavBar(progress: 0.0, buttonAction: true) + self?.animateNavBar(progress: 0.0) self?.updateTracking(progress: 0.0) } @@ -206,90 +217,31 @@ final class HomeViewController: UIViewController { mainView.utxosWalletButton.onTap = { [weak self] in self?.moveToUtxosWallet() } - - TariEventBus.onMainThread(self, eventType: .balanceUpdate) { [weak self] _ in - self?.safeRefreshBalance() - } - - NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) - .sink { [weak self] _ in self?.onAppMovedToForeground() } - .store(in: &cancelables) } // MARK: - Actions - private func showConectionStatusPopUp() { - ConnectionMonitor.shared.showDetailsPopup() - } - - private func onAppMovedToForeground() { - guard TariLib.shared.walletState != .started else { - txsTableVC.tableView.beginRefreshing() + private func checkIncompatibleNetwork() { + + guard !isNetworkCompatibilityChecked else { return } + isNetworkCompatibilityChecked = true + + guard model.isNetworkCompatible else { + showIncompatibleNetworkDialog() return } - tableDataReloadIsWaitingForWallet = true + + checkBackupPrompt(delay: 0) } - // MARK: - Other - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - TariEventBus.onMainThread(self, eventType: .walletStateChanged) { - [weak self] - (sender) in - let walletState = sender!.object as! TariLib.WalletState - guard let self = self else { return } - - switch walletState { - case .started: - if self.balanceRefreshIsWaitingForWallet { - self.balanceRefreshIsWaitingForWallet = false - self.safeRefreshBalance() - } - if self.tableDataReloadIsWaitingForWallet { - self.tableDataReloadIsWaitingForWallet = false - self.txsTableVC.tableView.beginRefreshing() - } - if self.networkCompatibilityCheckIsWaitingForWallet { - self.networkCompatibilityCheckIsWaitingForWallet = false - self.safeCheckIncompatibleNetwork() - } - default: - break - } - } - safeRefreshBalance() - ShortcutsManager.executeQueuedShortcut() - checkImportSecondUtxo() - safeCheckIncompatibleNetwork() - } - - private func safeCheckIncompatibleNetwork() { - if TariLib.shared.walletState != .started { - networkCompatibilityCheckIsWaitingForWallet = true - return - } - do { - let persistedNetwork = try TariLib.shared.tariWallet?.getKeyValue(key: TariLib.KeyValueStorageKeys.network.rawValue) - if persistedNetwork != NetworkManager.shared.selectedNetwork.name { - // incompatible network - displayIncompatibleNetworkDialog() - } else { - checkBackupPrompt(delay: 0) - } - } catch { - // no-op - } - } - - private func displayIncompatibleNetworkDialog() { + private func showIncompatibleNetworkDialog() { let popUpModel = PopUpDialogModel( title: localized("incompatible_network.title"), message: localized("incompatible_network.description"), buttons: [ PopUpDialogButtonModel(title: localized("incompatible_network.confirm"), type: .normal, callback: { [weak self] in self?.deleteWallet() }), PopUpDialogButtonModel(title: localized("incompatible_network.cancel"), type: .text, callback: { [weak self] in - try? TariLib.shared.setCurrentNetworkKeyValue() + self?.model.updateCompatibleNetworkName() self?.checkBackupPrompt(delay: 2.0) }) ], @@ -297,42 +249,69 @@ final class HomeViewController: UIViewController { ) PopUpPresenter.showPopUp(model: popUpModel) } - + private func deleteWallet() { - TariLib.shared.deleteWallet() - BackupScheduler.shared.stopObserveEvents() - // go back to splash screen - let navigationController = AlwaysPoppableNavigationController( - rootViewController: SplashViewController() + model.deleteWallet() + AppRouter.transitionToSplashScreen() + } + + private func showConectionStatusPopUp() { + Tari.shared.connectionMonitor.showDetailsPopup() + } + + private func update(balance: String) { + + let balanceLabelAttributedText = NSMutableAttributedString( + string: balance, + attributes: [ + .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabel, + .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel! + ] ) - navigationController.setNavigationBarHidden( - true, - animated: false + + let lastNumberOfDigitsToFormat = MicroTari.roundedFractionDigits + 1 + + balanceLabelAttributedText.addAttributes( + [ + .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabelDecimals, + .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel!, + .baselineOffset: 5.0 + ], + range: NSRange(location: balance.count - lastNumberOfDigitsToFormat, length: lastNumberOfDigitsToFormat) ) - UIApplication.shared.windows.first?.rootViewController = navigationController - UIApplication.shared.windows.first?.makeKeyAndVisible() - } - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - TariEventBus.unregister(self) + balanceLabelAttributedText.addAttributes( + [NSAttributedString.Key.kern: 1.1], + range: NSRange(location: balance.count - lastNumberOfDigitsToFormat - 1 ,length: 1) + ) + + mainView.balanceValueLabel.attributedText = balanceLabelAttributedText + checkBackupPrompt(delay: 2) } + + private func update(avaiableToSpendAmount: String) { + + let text = NSAttributedString(string: avaiableToSpendAmount, attributes: [ + .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabelDecimals, + .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel ?? UIColor() + ]) - override var preferredStatusBarStyle: UIStatusBarStyle { - return isTxViewFullScreen ? .darkContent : .lightContent + mainView.avaiableFoundsValueLabel.attributedText = text } + + // MARK: - Other private func setupKeyServer() { do { keyServer = try KeyServer() } catch { - TariLogger.error("Failed to initialise KeyServer") + Logger.log(message: "Failed to initialise KeyServer", domain: .general, level: .error) } } private func requestKeyServerTokens() { guard let keyServer = keyServer else { - TariLogger.error("No KeyServer initialised") + Logger.log(message: "No KeyServer initialised", domain: .general, level: .error) return } @@ -358,8 +337,10 @@ final class HomeViewController: UIViewController { ], hapticType: .none ) - + + DispatchQueue.main.async { PopUpPresenter.showPopUp(model: popUpModel) + } }) { (error) in DispatchQueue.main.async { PopUpPresenter.show(message: MessageModel(title: errorTitle, message: localized("home.request_drop.error.description"), type: .error)) @@ -373,7 +354,7 @@ final class HomeViewController: UIViewController { // If we have a second stored utxo, import it private func checkImportSecondUtxo() { guard let keyServer = keyServer else { - TariLogger.error("No KeyServer initialised") + Logger.log(message: "No KeyServer initialised", domain: .general, level: .error) return } @@ -382,78 +363,10 @@ final class HomeViewController: UIViewController { PopUpPresenter.showStorePopUp() } } catch { - TariLogger.error("Failed to import 2nd UTXO", error: error) + Logger.log(message: "Failed to import 2nd UTXO: \(error.localizedDescription)", domain: .general, level: .error) } } - private func safeRefreshBalance() { - - guard TariLib.shared.walletState == .started else { - balanceRefreshIsWaitingForWallet = true - return - } - - do { - try refreshBalance() - try updateAvaiableToSpendAmount() - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("home.error.update_balance"), message: nil, type: .error)) - } - } - - private func refreshBalance() throws { - - let formattedValue = try TariLib.shared.tariWallet!.totalBalance.formatted - let balanceLabelAttributedText = NSMutableAttributedString( - string: formattedValue, - attributes: [ - .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabel, - .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel! - ] - ) - - let lastNumberOfDigitsToFormat = MicroTari.ROUNDED_FRACTION_DIGITS + 1 - balanceLabelAttributedText.addAttributes( - [ - .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabelDecimals, - .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel!, - .baselineOffset: 5.0 - ], - range: NSRange( - location: formattedValue.count - lastNumberOfDigitsToFormat, - length: lastNumberOfDigitsToFormat - ) - ) - - balanceLabelAttributedText.addAttributes( - [NSAttributedString.Key.kern: 1.1], - range: NSRange( - location: formattedValue.count - lastNumberOfDigitsToFormat - 1, - length: 1 - ) - ) - - mainView.balanceValueLabel.attributedText = balanceLabelAttributedText - - checkBackupPrompt(delay: 2) - } - - private func updateAvaiableToSpendAmount() throws { - - let formattedValue = try TariLib.shared.tariWallet!.availableBalance.formatted - let text = NSMutableAttributedString(string: formattedValue) - - text.addAttributes( - [ - .font: Theme.shared.fonts.homeScreenTotalBalanceValueLabelDecimals, - .foregroundColor: Theme.shared.colors.homeScreenTotalBalanceValueLabel! - ], - range: NSRange(location: 0, length: formattedValue.count) - ) - - mainView.avaiableFoundsValueLabel.attributedText = text - } - private func showHideFullScreen() { if isTxViewFullScreen { // Don't show header for first intro @@ -492,7 +405,7 @@ final class HomeViewController: UIViewController { // User swipes down for the first time if isFirstIntroToWallet { - TariSettings.shared.walletSettings.configationState = .ready + TariSettings.shared.walletSettings.configurationState = .ready } navigationController?.setNavigationBarHidden(true, animated: true) @@ -530,12 +443,8 @@ final class HomeViewController: UIViewController { // MARK: - TxTableDelegateMethods extension HomeViewController: TxsTableViewDelegate { - func onTxSelect(_ tx: Any) { - selectedTx = tx as? TxProtocol - - guard let transaction = selectedTx else { return } - - let controller = TransactionDetailsConstructor.buildScene(transaction: transaction) + func onTxSelect(_ tx: Transaction) { + let controller = TransactionDetailsConstructor.buildScene(transaction: tx) navigationController?.pushViewController(controller, animated: true) } @@ -583,7 +492,6 @@ extension HomeViewController: FloatingPanelControllerDelegate { } func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { - lastFPCPosition = vc.position txsTableVC.tableView.lockScrollView() self.impactFeedbackGenerator.prepare() } @@ -669,7 +577,7 @@ extension HomeViewController: FloatingPanelControllerDelegate { return progress } - private func animateNavBar(progress: CGFloat, buttonAction: Bool = false) { + private func animateNavBar(progress: CGFloat) { if progress >= 0.0 && progress <= 1.0 { mainView.toolbarBottomConstraint?.constant = navBarHeight * progress UIView.animate( @@ -741,6 +649,7 @@ private extension PopUpPresenter { headerSection.imageHeight = 180.0 headerSection.imageView.image = Theme.shared.images.storeModal + headerSection.label.attributedText = storePopUpTitle() let contentSection = PopUpComponentsFactory.makeContentView(message: localized("store_modal.description", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol)) let buttonsSection = PopUpComponentsFactory.makeButtonsView(models: [ @@ -771,6 +680,6 @@ private extension PopUpPresenter { private static func openStoreWebpage() { guard let url = URL(string: TariSettings.shared.storeUrl) else { return } WebBrowserPresenter.open(url: url) - TariLogger.verbose("Opened store link") + Logger.log(message: "Opened store link", domain: .general, level: .verbose) } } diff --git a/MobileWallet/Screens/Home/Home/HomeViewModel.swift b/MobileWallet/Screens/Home/Home/HomeViewModel.swift index 87468a4c..77c14a23 100644 --- a/MobileWallet/Screens/Home/Home/HomeViewModel.swift +++ b/MobileWallet/Screens/Home/Home/HomeViewModel.swift @@ -52,6 +52,9 @@ final class HomeViewModel { // MARK: - View Model @Published private(set) var connectionStatusImage: UIImage? + @Published private(set) var balance: String = "" + @Published private(set) var availableBalance: String = "" + @Published private(set) var isNetworkCompatible: Bool = true // MARK: - Properties @@ -61,42 +64,76 @@ final class HomeViewModel { init() { setupCallbacks() + checkNetworkCompatibility() } // MARK: - Setups private func setupCallbacks() { - ConnectionMonitor.shared.$status - .compactMap { $0 } - .sink { [weak self] in self?.handle(connectionStatus: $0) } + + let monitor = Tari.shared.connectionMonitor + + Publishers.CombineLatest4(monitor.$networkConnection, monitor.$torConnection, monitor.$baseNodeConnection, monitor.$syncStatus) + .sink { [weak self] in self?.handle(networkConnection: $0, torConnection: $1, baseNodeConnection: $2, syncStatus: $3) } + .store(in: &cancellables) + + Tari.shared.walletBalance.$balance + .sink { [weak self] in self?.handle(walletBalance: $0) } .store(in: &cancellables) } + // MARK: - Actions + + func updateCompatibleNetworkName() { + _ = try? Tari.shared.keyValues.set(key: .network, value: NetworkManager.shared.selectedNetwork.name) + } + + func deleteWallet() { + Tari.shared.deleteWallet() + BackupScheduler.shared.stopObserveEvents() + } + + private func checkNetworkCompatibility() { + guard let persistedNetworkName = try? Tari.shared.keyValues.value(key: .network), persistedNetworkName == NetworkManager.shared.selectedNetwork.name else { + isNetworkCompatible = false + return + } + + isNetworkCompatible = true + } + // MARK: - Helpers - private func handle(connectionStatus: ConnectionMonitor.StatusModel) { + private func handle(networkConnection: NetworkMonitor.Status, torConnection: TorManager.ConnectionStatus, baseNodeConnection: BaseNodeConnectivityStatus, syncStatus: TariValidationService.SyncStatus) { - switch (connectionStatus.networkConnection, connectionStatus.torConnection, connectionStatus.baseNodeConnectivity, connectionStatus.syncStatus) { + switch (networkConnection, torConnection, baseNodeConnection, syncStatus) { case (.disconnected, _, _, _): connectionStatusImage = offlineIcon case (.connected, .disconnected, _, _): connectionStatusImage = offlineIcon - case (.connected, .failed, _, _): + case (.connected, .disconnecting, _, _): connectionStatusImage = offlineIcon case (.connected, .connecting, _, _): connectionStatusImage = limitedConnectionIcon + case (.connected, .portsOpen, _, _): + connectionStatusImage = limitedConnectionIcon case (.connected, .connected, .offline, _): connectionStatusImage = limitedConnectionIcon case (.connected, .connected, .connecting, _): connectionStatusImage = limitedConnectionIcon case (.connected, .connected, .online, .idle): connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .online, .failure): + case (.connected, .connected, .online, .failed): connectionStatusImage = limitedConnectionIcon - case (.connected, .connected, .online, .pending): + case (.connected, .connected, .online, .syncing): connectionStatusImage = onlineIcon - case (.connected, .connected, .online, .success): + case (.connected, .connected, .online, .synced): connectionStatusImage = onlineIcon } } + + private func handle(walletBalance: WalletBalance) { + balance = MicroTari(walletBalance.available + walletBalance.incoming).formatted + availableBalance = MicroTari(walletBalance.available).formatted + } } diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsConstructor.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsConstructor.swift index 0d354823..f601abba 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsConstructor.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsConstructor.swift @@ -40,7 +40,7 @@ enum TransactionDetailsConstructor { - static func buildScene(transaction: TxProtocol) -> TransactionDetailsViewController { + static func buildScene(transaction: Transaction) -> TransactionDetailsViewController { let model = TransactionDetailsModel(transaction: transaction) return TransactionDetailsViewController(model: model) } diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift index d9d387c7..31dc078e 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsModel.swift @@ -43,10 +43,6 @@ import GiphyCoreSDK final class TransactionDetailsModel { - private enum ModelError: Error { - case generic - } - // MARK: - View Model @Published private(set) var title: String? @@ -72,7 +68,7 @@ final class TransactionDetailsModel { // MARK: - Properties - private var transaction: TxProtocol + private var transaction: Transaction private var transactionNounce: String? private var transactionSignature: String? @@ -80,45 +76,42 @@ final class TransactionDetailsModel { // MARK: - Initialisers - init(transaction: TxProtocol) { + init(transaction: Transaction) { self.transaction = transaction + setupCallbacks() + fetchData() } // MARK: - Setups private func setupCallbacks() { - let eventTypes: [TariEventTypes] = [ - .receievedTxReply, - .receivedFinalizedTx, - .txBroadcast, - .txMinedUnconfirmed, - .txMined + + let events = [ + WalletCallbacksManager.shared.receivedTransactionReply, + WalletCallbacksManager.shared.receivedFinalizedTransaction, + WalletCallbacksManager.shared.transactionBroadcast, + WalletCallbacksManager.shared.unconfirmedTransactionMined, + WalletCallbacksManager.shared.transactionMined ] - eventTypes.forEach { - TariEventBus.events(forType: $0) - .compactMap(\.object) - .compactMap { $0 as? TxProtocol } + events.forEach { + $0 + .filter { [unowned self] in (try? $0.identifier) == (try? self.transaction.identifier) } .sink { [weak self] in self?.handle(transaction: $0) } .store(in: &cancellables) } } - private func handle(transaction: TxProtocol) { - self.transaction = transaction - fetchData() - } - // MARK: - Actions - func fetchData() { - title = fetchTitle() - transactionState = fetchTransactionState() - transactionDirection = fetchTransactionDirection() - emojiIdViewModel = fetchEmojiIdViewModel() - isContactSectionVisible = !transaction.isOneSidedPayment + private func fetchData() { do { + title = try fetchTitle() + transactionState = try fetchTransactionState() + transactionDirection = try fetchTransactionDirection() + emojiIdViewModel = try fetchEmojiIdViewModel() + isContactSectionVisible = try !transaction.isOneSidedPayment subtitle = try fetchSubtitle() amount = try fetchAmount() fee = try fetchFee() @@ -136,14 +129,12 @@ final class TransactionDetailsModel { func cancelTransactionRequest() { - guard transaction.status.0 == .pending, transaction.direction == .outbound else { - errorModel = MessageModel(title: localized("tx_detail.tx_cancellation.error.title"), message: localized("tx_detail.tx_cancellation.error.description"), type: .error) - return - } - do { - try TariLib.shared.tariWallet?.cancelPendingTx(transaction) - wasTransactionCanceled = true + guard try transaction.status == .pending, try transaction.isOutboundTransaction else { + errorModel = MessageModel(title: localized("tx_detail.tx_cancellation.error.title"), message: localized("tx_detail.tx_cancellation.error.description"), type: .error) + return + } + wasTransactionCanceled = try Tari.shared.transactions.cancelPendingTransaction(identifier: transaction.identifier) } catch { errorModel = MessageModel(title: localized("tx_detail.tx_cancellation.error.title"), message: nil, type: .error) } @@ -156,10 +147,12 @@ final class TransactionDetailsModel { func update(alias: String?) { - guard let alias = alias, !alias.isEmpty, let hex = emojiIdViewModel?.hex, let wallet = TariLib.shared.tariWallet else { return } + guard let alias = alias, !alias.isEmpty else { return } do { - try wallet.addUpdateContact(alias: alias, publicKeyHex: hex) + let publicKey = try transaction.publicKey + let contact = try Contact(alias: alias, publicKeyPointer: publicKey.pointer) + _ = try Tari.shared.contacts.upsert(contact: contact) userAliasUpdateSuccessCallback?() userAlias = alias } catch { @@ -178,38 +171,31 @@ final class TransactionDetailsModel { // MARK: - Helpers - private func fetchTitle() -> String? { + private func fetchTitle() throws -> String? { if transaction.isCancelled { return localized("tx_detail.payment_cancelled") } - switch transaction.status.0 { + switch try transaction.status { case .txNullError, .completed, .broadcast, .minedUnconfirmed, .pending, .unknown: return localized("tx_detail.payment_in_progress") case .minedConfirmed, .imported, .rejected, .fauxUnconfirmed, .fauxConfirmed: - break + return try transaction.isOutboundTransaction ? localized("tx_detail.payment_sent") : localized("tx_detail.payment_received") } - - switch transaction.direction { - case .inbound: - return localized("tx_detail.payment_received") - case .outbound: - return localized("tx_detail.payment_sent") - case .none: - break - } - - return nil } private func fetchSubtitle() throws -> String { - let date = transaction.date.0 + var formattedDate: String? + + if let timestamp = try? transaction.timestamp { + formattedDate = Date(timeIntervalSince1970: TimeInterval(timestamp)).formattedDisplay() + } + var failureReason: String? - let formattedDate = date?.formattedDisplay() - if let completedTransaction = transaction as? CompletedTx, let description = try completedTransaction.rejectionReason.description { + if let completedTransaction = transaction as? CompletedTransaction, let description = try completedTransaction.rejectionReason.description { failureReason = description } @@ -220,104 +206,67 @@ final class TransactionDetailsModel { .joined(separator: "\n") } - private func fetchTransactionState() -> AnimatedRefreshingViewState? { + private func fetchTransactionState() throws -> AnimatedRefreshingViewState? { guard !transaction.isCancelled else { return nil } - switch transaction.status.0 { + switch try transaction.status { case .pending: - switch transaction.direction { - case .inbound: - return .txWaitingForSender - case .outbound: - return .txWaitingForRecipient - case .none: - return nil - } + return try transaction.isOutboundTransaction ? .txWaitingForRecipient : .txWaitingForSender case .broadcast, .completed: return .txCompleted(confirmationCount: 1) case .minedUnconfirmed: - guard let confirmationCountTuple = (transaction as? CompletedTx)?.confirmationCount, confirmationCountTuple.1 == nil else { + guard let confirmationCount = try (transaction as? CompletedTransaction)?.confirmationCount else { return .txCompleted(confirmationCount: 1) } - return .txCompleted(confirmationCount: confirmationCountTuple.0 + 1) + return .txCompleted(confirmationCount: confirmationCount + 1) case .txNullError, .imported, .minedConfirmed, .unknown, .rejected, .fauxUnconfirmed, .fauxConfirmed: return nil } } - private func fetchTransactionDirection() -> String? { - switch transaction.direction { - case .inbound: - return localized("tx_detail.from") - case .outbound: - return localized("tx_detail.to") - case .none: - return nil - } + private func fetchTransactionDirection() throws -> String? { + try transaction.isOutboundTransaction ? localized("tx_detail.to") : localized("tx_detail.from") } private func fetchAmount() throws -> String { - - guard let amount = transaction.microTari.0 else { - throw transaction.microTari.1 ?? ModelError.generic - } - - return amount.formattedPrecise + let amount = try transaction.amount + return MicroTari(amount).formattedPrecise } private func fetchFee() throws -> String? { - - guard transaction.direction == .outbound, let fee = (transaction as? CompletedTx)?.fee ?? (transaction as? PendingOutboundTx)?.fee else { - return nil - } - - if let error = fee.1 { - throw error - } - - return fee.0?.formattedWithOperator + guard try transaction.isOutboundTransaction, let fee = try (transaction as? CompletedTransaction)?.fee ?? (transaction as? PendingOutboundTransaction)?.fee else { return nil } + return MicroTari(fee).formattedWithOperator } - private func fetchEmojiIdViewModel() -> EmojiIdView.ViewModel? { - - var emojiID: String? - var hex: String? - - switch transaction.direction { - case .inbound: - emojiID = transaction.sourcePublicKey.0?.emojis.0 - hex = transaction.sourcePublicKey.0?.hex.0 - case .outbound: - emojiID = transaction.destinationPublicKey.0?.emojis.0 - hex = transaction.destinationPublicKey.0?.hex.0 - case .none: - return nil - } - - guard let emojiID = emojiID, let hex = hex else { return nil } + private func fetchEmojiIdViewModel() throws -> EmojiIdView.ViewModel { + let publicKey = try transaction.publicKey + let emojiID = try publicKey.emojis + let hex = try publicKey.byteVector.hex return EmojiIdView.ViewModel(emojiID: emojiID, hex: hex) } private func fetchUserAlias() throws -> String? { - guard let contact = transaction.contact.0 else { return nil } - if let aliasError = contact.alias.1 { throw aliasError } - return contact.alias.0 + let contact = try Tari.shared.contacts.findContact(hex: try transaction.publicKey.byteVector.hex) + return try contact?.alias + } + + private func handle(transaction: Transaction) { + self.transaction = transaction + fetchData() } private func handleMessage() throws { - guard !transaction.isOneSidedPayment else { + guard try !transaction.isOneSidedPayment else { note = localized("transaction.one_sided_payment.note.normal") gifMedia = nil return } - if let messageError = transaction.message.1 { throw messageError } - - let message = transaction.message.0 + let message = try transaction.message let giphyLinkPrefix = "https://giphy.com/embed/" guard let endIndex = message.range(of: giphyLinkPrefix)?.lowerBound else { @@ -333,7 +282,7 @@ final class TransactionDetailsModel { GiphyCore.shared.gifByID(gifID) { [weak self] response, error in if let error = error { - TariLogger.error("Failed to load gif", error: error) + Logger.log(message: "Failed to load gif: \(error.localizedDescription)", domain: .general, level: .error) return } @@ -350,14 +299,14 @@ final class TransactionDetailsModel { isBlockExplorerActionAvailable = transactionNounce != nil && transactionSignature != nil } - guard let kernel = transaction.transactionKernel.0 else { + guard let kernel = try? (transaction as? CompletedTransaction)?.transactionKernel else { transactionNounce = nil transactionSignature = nil return } - transactionNounce = try? kernel.excessPublicNonce - transactionSignature = try? kernel.excessSignature + transactionNounce = try? kernel.excessPublicNonceHex + transactionSignature = try? kernel.excessSignatureHex } private func fetchLinkToOpen() -> URL? { @@ -367,7 +316,7 @@ final class TransactionDetailsModel { } } -private extension TransactionRejectionReason { +private extension CompletedTransaction.RejectionReason { var description: String? { switch self { diff --git a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift index 06d4289a..e9c983ae 100644 --- a/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift +++ b/MobileWallet/Screens/Home/Transaction Details/TransactionDetailsViewController.swift @@ -71,9 +71,6 @@ final class TransactionDetailsViewController: UIViewController { super.viewDidLoad() setupCallbacks() hideKeyboardWhenTappedAroundOrSwipedDown() - model.fetchData() - - Tracker.shared.track("/home/tx_details", "Transaction Details") } // MARK: - Setups @@ -81,44 +78,54 @@ final class TransactionDetailsViewController: UIViewController { private func setupCallbacks() { model.$title + .receive(on: DispatchQueue.main) .assign(to: \.title, on: mainView.navigationBar) .store(in: &cancellables) Publishers.Zip(model.$subtitle, model.$isFailure) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.navigationBar.update(subtitle: $0, isCompact: $1) } .store(in: &cancellables) model.$transactionState + .receive(on: DispatchQueue.main) .assign(to: \.transactionState, on: mainView) .store(in: &cancellables) model.$amount + .receive(on: DispatchQueue.main) .assign(to: \.text, on: mainView.valueView.valueLabel) .store(in: &cancellables) model.$fee + .receive(on: DispatchQueue.main) .assign(to: \.fee, on: mainView.valueView) .store(in: &cancellables) model.$transactionDirection + .receive(on: DispatchQueue.main) .assign(to: \.title, on: mainView.contactView) .store(in: &cancellables) model.$emojiIdViewModel + .receive(on: DispatchQueue.main) .assign(to: \.emojiIdViewModel, on: mainView.contactView.contentView) .store(in: &cancellables) model.$isContactSectionVisible .map { !$0 } + .receive(on: DispatchQueue.main) .assign(to: \.isHidden, on: mainView.contactView) .store(in: &cancellables) model.$isAddContactButtonVisible .map { !$0 } + .receive(on: DispatchQueue.main) .assign(to: \.isHidden, on: mainView.contactView.contentView.addContactButton) .store(in: &cancellables) model.$isNameSectionVisible + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.contactNameView.isHidden = !$0 self?.mainView.noteSeparatorView.isHidden = !$0 @@ -128,20 +135,23 @@ final class TransactionDetailsViewController: UIViewController { .store(in: &cancellables) model.$userAlias + .receive(on: DispatchQueue.main) .assign(to: \.text, on: mainView.contactNameView.contentView.textField) .store(in: &cancellables) model.$note + .receive(on: DispatchQueue.main) .assign(to: \.note, on: mainView.noteView.contentView) .store(in: &cancellables) model.$gifMedia - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.gifMedia, on: mainView.noteView.contentView) .store(in: &cancellables) model.$wasTransactionCanceled .filter { $0 } + .receive(on: DispatchQueue.main) .sink { [weak self] _ in UINotificationFeedbackGenerator().notificationOccurred(.success) self?.navigationController?.popToRootViewController(animated: true) @@ -150,6 +160,7 @@ final class TransactionDetailsViewController: UIViewController { model.$isBlockExplorerActionAvailable .map { !$0 } + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.blockExplorerSeparatorView.isHidden = $0 self?.mainView.blockExplorerView.isHidden = $0 @@ -158,11 +169,13 @@ final class TransactionDetailsViewController: UIViewController { model.$linkToOpen .compactMap { $0 } + .receive(on: DispatchQueue.main) .sink { WebBrowserPresenter.open(url: $0) } .store(in: &cancellables) model.$errorModel .compactMap { $0 } + .receive(on: DispatchQueue.main) .sink { PopUpPresenter.show(message: $0) } .store(in: &cancellables) @@ -203,12 +216,17 @@ final class TransactionDetailsViewController: UIViewController { } private func showTransactionCancellationConfirmation() { - let controller = UIAlertController(title: localized("tx_detail.tx_cancellation.title"), message: localized("tx_detail.tx_cancellation.message"), preferredStyle: .alert) - controller.addAction(UIAlertAction(title: localized("tx_detail.tx_cancellation.yes"), style: .destructive, handler: { [weak self] _ in - self?.model.cancelTransactionRequest() - })) - controller.addAction(UIAlertAction(title: localized("tx_detail.tx_cancellation.no"), style: .cancel)) - present(controller, animated: true) + let model = PopUpDialogModel( + title: localized("tx_detail.tx_cancellation.title"), + message: localized("tx_detail.tx_cancellation.message"), + buttons: [ + PopUpDialogButtonModel(title: localized("tx_detail.tx_cancellation.yes"), type: .destructive, callback: { [weak self] in self?.model.cancelTransactionRequest() }), + PopUpDialogButtonModel(title: localized("tx_detail.tx_cancellation.no"), type: .text) + ], + hapticType: .none + ) + + PopUpPresenter.showPopUp(model: model) } } diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxGifManager.swift b/MobileWallet/Screens/Home/TransactionHistory/TxGifManager.swift index da86c4d3..56e36d8f 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxGifManager.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxGifManager.swift @@ -81,9 +81,9 @@ class TxGifManager { let downloadOperation = GiphyCore.shared.gifByID(gifID) { [weak self] (response, error) in self?.operations.removeValue(forKey: gifID) - guard error == nil else { - TariLogger.error("Failed to load gif", error: error) - self?.completions[gifID]?.forEach({ $0(.failure(error!)) }) + if let error { + Logger.log(message: "Failed to load gif: \(error.localizedDescription)", domain: .general, level: .error) + self?.completions[gifID]?.forEach({ $0(.failure(error)) }) self?.completions.removeValue(forKey: gifID) return } diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift index 5d968247..7fa4af60 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewCell.swift @@ -88,7 +88,7 @@ class TxTableViewCell: UITableViewCell { setStatus(model.status) setValue( microTari: model.value.microTari, - direction: model.value.direction, + isOutboundTransaction: model.value.isOutboundTransaction, isCancelled: model.value.isCancelled, isPending: model.value.isPending ) @@ -133,7 +133,7 @@ class TxTableViewCell: UITableViewCell { self.setStatus(item.status) self.setValue( microTari: model.value.microTari, - direction: model.tx.direction, + isOutboundTransaction: model.value.isOutboundTransaction, isCancelled: model.value.isCancelled, isPending: model.value.isPending ) @@ -183,20 +183,20 @@ class TxTableViewCell: UITableViewCell { } } - private func setValue(microTari: MicroTari?, direction: TxDirection, isCancelled: Bool, isPending: Bool) { + private func setValue(microTari: MicroTari?, isOutboundTransaction: Bool, isCancelled: Bool, isPending: Bool) { if let mt = microTari { if isCancelled { valueLabel.text = mt.formattedPrecise valueLabel.backgroundColor = Theme.shared.colors.txCellValueCancelledBackground valueLabel.textColor = Theme.shared.colors.txCellValueCancelledText - } else if direction == .inbound { - valueLabel.text = mt.formattedWithOperator - valueLabel.backgroundColor = Theme.shared.colors.txCellValuePositiveBackground - valueLabel.textColor = Theme.shared.colors.txCellValuePositiveText - } else if direction == .outbound { + } else if isOutboundTransaction { valueLabel.text = mt.formattedWithNegativeOperator valueLabel.backgroundColor = Theme.shared.colors.txCellValueNegativeBackground valueLabel.textColor = Theme.shared.colors.txCellValueNegativeText + } else { + valueLabel.text = mt.formattedWithOperator + valueLabel.backgroundColor = Theme.shared.colors.txCellValuePositiveBackground + valueLabel.textColor = Theme.shared.colors.txCellValuePositiveText } if isPending && !isCancelled { diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift index 57cbb1be..c0f52b82 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxTableViewModel.swift @@ -38,15 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation import GiphyUISDK import GiphyCoreSDK class TxTableViewModel: NSObject { - typealias Value = (microTari: MicroTari?, direction: TxDirection, isCancelled: Bool, isPending: Bool) + typealias Value = (microTari: MicroTari?, isOutboundTransaction: Bool, isCancelled: Bool, isPending: Bool) let id: UInt64 - private(set) var tx: TxProtocol + private(set) var transaction: Transaction private(set) var title = NSAttributedString() private(set) var avatar: String = "" private(set) var message: String @@ -66,32 +65,32 @@ class TxTableViewModel: NSObject { @objc dynamic private(set) var gif: GPHMedia? @objc dynamic private(set) var status: String = "" @objc dynamic private(set) var time: String - - required init(tx: TxProtocol) { - self.tx = tx - self.id = tx.id.0 - - value = (microTari: tx.microTari.0, direction: tx.direction, isCancelled: tx.isCancelled, isPending: tx.isPending) + + init(transaction: Transaction) throws { + self.transaction = transaction + self.id = try transaction.identifier + + value = (microTari: MicroTari(try transaction.amount), isOutboundTransaction: try transaction.isOutboundTransaction, isCancelled: transaction.isCancelled, transaction.isPending) - if tx.isOneSidedPayment { + if try transaction.isOneSidedPayment { message = localized("transaction.one_sided_payment.note.normal") gifID = nil } else { - let (msg, giphyId) = TxTableViewModel.extractNote(from: tx.message.0) + let (msg, giphyId) = TxTableViewModel.extractNote(from: try transaction.message) message = msg gifID = giphyId } - time = tx.date.0?.relativeDayFromToday() ?? "" + time = try transaction.formattedTimestamp super.init() - updateTitleAndAvatar() - updateStatus() + try updateTitleAndAvatar() + try updateStatus() updateMedia() - + Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] (_) in - self?.time = tx.date.0?.relativeDayFromToday() ?? "" + self?.time = (try? transaction.formattedTimestamp) ?? "" } } @@ -108,65 +107,50 @@ class TxTableViewModel: NSObject { } } - func update(tx: TxProtocol) { - if tx.id.0 != self.tx.id.0 { fatalError() } - self.tx = tx - self.value = ( - microTari: tx.microTari.0, - direction: tx.direction, - isCancelled: tx.isCancelled, - isPending: tx.isPending - ) - updateTitleAndAvatar() - updateStatus() + func update(transaction: Transaction) throws { + + guard try id == transaction.identifier else { return } + self.transaction = transaction + + value = (microTari: MicroTari(try transaction.amount), isOutboundTransaction: try transaction.isOutboundTransaction, isCancelled: transaction.isCancelled, transaction.isPending) + + try updateTitleAndAvatar() + try updateStatus() updateMedia() } - private func updateTitleAndAvatar() { + private func updateTitleAndAvatar() throws { - guard !tx.isOneSidedPayment else { + guard try !transaction.isOneSidedPayment else { avatar = localized("transaction.one_sided_payment.avatar") let alias = localized("transaction.one_sided_payment.inbound_user_placeholder") title = attributed(title: localized("tx_list.inbound_pending_title", arguments: alias), withAlias: alias, isAliasEmojiID: false) return } - let (publicKey, _) = tx.direction == .inbound ? tx.sourcePublicKey : tx.destinationPublicKey - guard let pubKey = publicKey else { fatalError() } + let publicKey = try transaction.publicKey - let (emojis, _) = pubKey.emojis - avatar = String(emojis.emojis.prefix(1)) + let emojis = try publicKey.emojis + avatar = String(emojis.prefix(1)) var alias = "" var aliasIsEmojis = false - if let contact = tx.contact.0 { - alias = contact.alias.0 + + if let contact = try Tari.shared.contacts.findContact(hex: try publicKey.byteVector.hex) { + alias = try contact.alias } if alias.isEmpty { - let (emojis, _) = pubKey.emojis - alias = "\(String(emojis.emojis.prefix(2)))โ€ขโ€ขโ€ข\(String(emojis.emojis.suffix(2)))" + alias = "\(String(emojis.prefix(2)))โ€ขโ€ขโ€ข\(String(emojis.suffix(2)))" aliasIsEmojis = true } var titleText = "" - if tx.direction == .inbound { - if tx.status.0 == .pending { - titleText = String( - format: localized("tx_list.inbound_pending_title"), - alias - ) - } else { - titleText = String( - format: localized("tx_list.inbound_title"), - alias - ) - } - } else if tx.direction == .outbound { - titleText = String( - format: localized("tx_list.outbound_title"), - alias - ) + + if try transaction.isOutboundTransaction { + titleText = localized("tx_list.outbound_title", arguments: alias) + } else { + titleText = try transaction.status == .pending ? localized("tx_list.inbound_pending_title", arguments: alias) : localized("tx_list.inbound_title", arguments: alias) } title = attributed(title: titleText, withAlias: alias, isAliasEmojiID: aliasIsEmojis) @@ -174,7 +158,7 @@ class TxTableViewModel: NSObject { private func attributed(title: String, withAlias alias: String, isAliasEmojiID: Bool) -> NSAttributedString { - let title = title.replacingOccurrences(of: " ", with: "\u{00A0}") + let title = title .replacingOccurrences(of: alias, with: " \(alias) ") .trimmingCharacters(in: .whitespaces) @@ -200,45 +184,29 @@ class TxTableViewModel: NSObject { return attributedTitle } - private func updateStatus() { + private func updateStatus() throws { var statusMessage = "" - switch tx.status.0 { + switch try transaction.status { case .pending: - if tx.direction == .inbound { - statusMessage = localized("refresh_view.waiting_for_sender") - } else if tx.direction == .outbound { - statusMessage = localized("refresh_view.waiting_for_recipient") - } + statusMessage = try transaction.isOutboundTransaction ? localized("refresh_view.waiting_for_recipient") : localized("refresh_view.waiting_for_sender") case .broadcast, .completed: - guard let wallet = TariLib.shared.tariWallet, - let requiredConfirmationCount = try? wallet.getRequiredConfirmationCount() else { + guard let requiredConfirmationCount = try? Tari.shared.transactions.requiredConfirmationsCount else { statusMessage = localized("refresh_view.final_processing") break } - statusMessage = String( - format: localized("refresh_view.final_processing_with_param"), - 1, - requiredConfirmationCount + 1 - ) + statusMessage = localized("refresh_view.final_processing_with_param", arguments: 1, requiredConfirmationCount + 1) case .minedUnconfirmed: - guard let wallet = TariLib.shared.tariWallet, - let confirmationCount = (tx as? CompletedTx)?.confirmationCount, - confirmationCount.1 == nil, - let requiredConfirmationCount = try? wallet.getRequiredConfirmationCount() else { + guard let confirmationCount = try? (transaction as? CompletedTransaction)?.confirmationCount, let requiredConfirmationCount = try? Tari.shared.transactions.requiredConfirmationsCount else { statusMessage = localized("refresh_view.final_processing") break } - statusMessage = String( - format: localized("refresh_view.final_processing_with_param"), - confirmationCount.0 + 1, - requiredConfirmationCount + 1 - ) + statusMessage = localized("refresh_view.final_processing_with_param", arguments: confirmationCount + 1, requiredConfirmationCount + 1) default: break } - if tx.isCancelled { + if transaction.isCancelled { statusMessage = localized("tx_detail.payment_cancelled") } diff --git a/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift b/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift index a6e3ac70..07e6d96d 100644 --- a/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift +++ b/MobileWallet/Screens/Home/TransactionHistory/TxsListViewController.swift @@ -40,9 +40,10 @@ import UIKit import Lottie +import Combine protocol TxsTableViewDelegate: AnyObject { - func onTxSelect(_: Any) + func onTxSelect(_: Transaction) func onScrollTopHit(_: Bool) } @@ -88,6 +89,8 @@ final class TxsListViewController: UIViewController { private var hasCancelledTxWhileUpdating = false private var refreshTimeoutTimer: Timer? + private var transactionModelsCancellables = Set() + private var cancellables = Set() var backgroundType: BackgroundViewType = .none { didSet { @@ -108,29 +111,16 @@ final class TxsListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() viewSetup() - DispatchQueue.main.asyncAfter( - deadline: .now() + 1.0 + CATransaction.animationDuration() - ) { - [weak self] in - guard let self = self else { return } - self.registerEvents() - NotificationCenter.default.addObserver( - self, - selector: #selector(self.registerEvents), - name: UIApplication.willEnterForegroundNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(self.unregisterEvents), - name: UIApplication.didEnterBackgroundNotification, - object: nil - ) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0 + CATransaction.animationDuration()) { [weak self] in + self?.setupEvents() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + + setupTransactionsCallbacks() + if backgroundType != .intro { safeRefreshTable() } @@ -142,71 +132,118 @@ final class TxsListViewController: UIViewController { animatedRefresher.stateType = .none } } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + cancelTransactionsCallbacks() + } func safeRefreshTable(_ completion:(() -> Void)? = nil) { - if TariLib.shared.walletState == .started { - refreshTable(completion) - } else { - TariEventBus.onMainThread(self, eventType: .walletStateChanged) { - [weak self] - (sender) in - guard let self = self else { return } - let walletState = sender!.object as! TariLib.WalletState - switch walletState { - case .started: - TariEventBus.unregister(self, eventType: .walletStateChanged) - self.refreshTable(completion) - case .startFailed: - TariEventBus.unregister(self, eventType: .walletStateChanged) - default: - break - } - } - } - } - - private func refreshTable(_ completion:(() -> Void)?) { txDataUpdateQueue.async(flags: .barrier) { - if self.fetchTx() == true { - DispatchQueue.main.async { - [weak self] in - self?.tableView.reloadData() - } - } - DispatchQueue.main.async { completion?() } + DispatchQueue.main.async { [weak self] in + self?.tableView.reloadData() + completion?() + } } } - - @discardableResult - private func fetchTx() -> Bool { - - guard let wallet = TariLib.shared.tariWallet else { return false } - - do { - var pendingTransactions: [TxProtocol] = try wallet.pendingInboundTransactions().list.0 - pendingTransactions += try wallet.pendingOutboundTransactions().list.0 - var completedTransactions: [TxProtocol] = try wallet.completedTransactions().list.0 - completedTransactions += try wallet.cancelledTransactions().list.0 - - pendingTxModels = pendingTransactions - .sorted { - guard let lDate = $0.date.0, let rDate = $1.date.0 else { return false } - return lDate > rDate - } - .map { TxTableViewModel(tx: $0) } - - completedTxModels = completedTransactions - .sorted { - guard let lDate = $0.date.0, let rDate = $1.date.0 else { return false } - return lDate > rDate - } - .map { TxTableViewModel(tx: $0) } - - return true - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("tx_list.error.grouped_txs.title"), message: localized("tx_list.error.grouped_txs.descritpion"), type: .error)) - return false - } + + private func setupTransactionsCallbacks() { + + Publishers.CombineLatest(Tari.shared.transactions.$pendingInbound, Tari.shared.transactions.$pendingOutbound) + .map { $0 as [Transaction] + $1 } + .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} + .tryMap { try $0.map { try TxTableViewModel(transaction: $0) }} + .replaceError(with: []) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.pendingTxModels = $0 } + .store(in: &transactionModelsCancellables) + + Publishers.CombineLatest(Tari.shared.transactions.$completed, Tari.shared.transactions.$cancelled) + .map { $0 + $1 } + .tryMap { try $0.sorted { try $0.timestamp > $1.timestamp }} + .tryMap { try $0.map { try TxTableViewModel(transaction: $0) }} + .replaceError(with: []) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.completedTxModels = $0 } + .store(in: &transactionModelsCancellables) + } + + private func cancelTransactionsCallbacks() { + transactionModelsCancellables.forEach { $0.cancel() } + transactionModelsCancellables.removeAll() + } + + private func setupEvents() { + + WalletCallbacksManager.shared.receivedTransaction + .sink { [weak self] _ in + self?.safeRefreshTable() + guard self?.animatedRefresher.stateType == .updateData else { return } + self?.hasReceivedTxWhileUpdating = true + } + .store(in: &cancellables) + + WalletCallbacksManager.shared.receivedTransactionReply + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.receivedFinalizedTransaction + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionBroadcast + .sink { [weak self] _ in + self?.safeRefreshTable() + guard self?.animatedRefresher.stateType == .updateData else { return } + self?.hasBroadcastTxWhileUpdating = true + } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionMined + .sink { [weak self] _ in + self?.safeRefreshTable() + guard self?.animatedRefresher.stateType == .updateData else { return } + self?.hasMinedTxWhileUpdating = true + } + .store(in: &cancellables) + + WalletCallbacksManager.shared.unconfirmedTransactionMined + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.fauxTransactionConfirmed + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.fauxTransactionUnconfirmed + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionSendResult + .sink { [weak self] _ in self?.safeRefreshTable() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionCancellation + .sink { [weak self] _ in + self?.safeRefreshTable() + guard self?.animatedRefresher.stateType == .updateData else { return } + self?.hasCancelledTxWhileUpdating = true + } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$syncStatus + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.handle(syncStatus: $0) } + .store(in: &cancellables) + + NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.animatedRefresher.animateOut() + self?.tableView.endRefreshing() + self?.animatedRefresher.stateType = .none + } + .store(in: &cancellables) } // MARK: - ViewModel related @@ -230,95 +267,58 @@ final class TxsListViewController: UIViewController { private func tableViewModel(forIndexPath indexPath: IndexPath) -> TxTableViewModel { tableViewModels(forSection: indexPath.section)[indexPath.row] } -} - -// MARK: AnimatedRefreshingView behavior -extension TxsListViewController { - func switchBaseNode(syncAfterSetting: Bool) throws { - - let currentBaseNode = NetworkManager.shared.selectedNetwork.selectedBaseNode - var newBaseNode: BaseNode! - - repeat { - newBaseNode = try NetworkManager.shared.selectedNetwork.randomNode() - } while newBaseNode == nil || currentBaseNode == newBaseNode; - - try TariLib.shared.update(baseNode: newBaseNode, syncAfterSetting: syncAfterSetting) - } - - private func onRefreshTimeout() { - TariLogger.info("Refresh has timed out.") - stopListeningToBaseNodeSync() - refreshTimeoutTimer?.invalidate() - refreshTimeoutTimer = nil - - do { - try switchBaseNode(syncAfterSetting: false) - beginRefreshing() - } catch { - endRefreshingWithSuccess() + // MARK: - Handlers + + private func handle(syncStatus: TariValidationService.SyncStatus) { + switch syncStatus { + case .idle: + break + case .syncing: + self.animateToSyncingState() + case .synced: + self.animateToSyncState() + case .failed: + break } } - - private func beginRefreshing() { - if animatedRefresher.stateType != .none { - return - } + + // MARK: - Animations + + private func animateToSyncingState() { + guard animatedRefresher.stateType == .none else { return } animatedRefresher.updateState(.loading, animated: false) animatedRefresher.animateIn() - - if refreshTimeoutTimer == nil { - refreshTimeoutTimer = Timer.scheduledTimer( - withTimeInterval: refreshTimeoutPeriodSecs, - repeats: false - ) { - [weak self] - (timer) in - timer.invalidate() - self?.onRefreshTimeout() - } - } - let connectionState = LegacyConnectionMonitor.shared.state - if connectionState.torBootstrapProgress == 100 { - syncBaseNode() - } else { - TariEventBus.onMainThread(self, eventType: .torConnectionProgress) { - [weak self] (result) in - guard let self = self else { return } - if let progress: Int = result?.object as? Int, progress == 100 { - TariEventBus.unregister(self, eventType: .torConnectionProgress) - TariEventBus.unregister(self, eventType: .torConnectionFailed) - self.beginRefreshing() + } + + private func animateToSyncState() { + animatedRefresher.stateType = .updateData + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in guard let self = self else { return } + if self.animatedRefresher.stateType != .none { + self.animatedRefresher.playUpdateSequence( + hasReceivedTx: self.hasReceivedTxWhileUpdating, + hasMinedTx: self.hasMinedTxWhileUpdating, + hasBroadcastTx: self.hasBroadcastTxWhileUpdating, + hasCancelledTx: self.hasCancelledTxWhileUpdating + ) { [weak self] in + self?.endRefreshingWithSuccess() } } - TariEventBus.onMainThread(self, eventType: .torConnectionFailed) { - [weak self] (_) in - guard let self = self else { return } - TariEventBus.unregister(self, eventType: .torConnectionProgress) - TariEventBus.unregister(self, eventType: .torConnectionFailed) - self.refreshTimeoutTimer?.invalidate() - self.refreshTimeoutTimer = nil - self.endRefreshingWithSuccess() - } + NotificationManager.shared.cancelAllFutureReminderNotifications() } } +} + +// MARK: AnimatedRefreshingView behavior +extension TxsListViewController { private func syncBaseNode() { do { - if let wallet = TariLib.shared.tariWallet { - hasReceivedTxWhileUpdating = false - hasBroadcastTxWhileUpdating = false - hasMinedTxWhileUpdating = false - hasCancelledTxWhileUpdating = false - startListeningToBaseNodeSync() - try wallet.syncBaseNode() - } else { - TariLogger.error("Cannot get wallet.") - refreshTimeoutTimer?.invalidate() - refreshTimeoutTimer = nil - endRefreshingWithSuccess() - } + try Tari.shared.validation.sync() + hasReceivedTxWhileUpdating = false + hasBroadcastTxWhileUpdating = false + hasMinedTxWhileUpdating = false + hasCancelledTxWhileUpdating = false } catch { refreshTimeoutTimer?.invalidate() refreshTimeoutTimer = nil @@ -381,7 +381,7 @@ extension TxsListViewController: UITableViewDelegate, UITableViewDataSource { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - actionDelegate?.onTxSelect(tableViewModel(forIndexPath: indexPath).tx) + actionDelegate?.onTxSelect(tableViewModel(forIndexPath: indexPath).transaction) } } @@ -403,121 +403,6 @@ extension TxsListViewController: UIScrollViewDelegate { } } -// MARK: TariBus events observation -extension TxsListViewController { - - @objc private func registerEvents() { - // Event for table refreshing - TariEventBus.onMainThread(self, eventType: .txListUpdate) { - [weak self] (_) in - guard let self = self else { return } - /* - if self.animatedRefresher.stateType != .updateData { - self.safeRefreshTable() - } - */ - // temporary :: display all changes immediately - // regardless of refresh status - self.safeRefreshTable() - } - - TariEventBus.onBackgroundThread(self, eventType: .receivedTx) { - [weak self] (_) in - guard let self = self else { return } - if self.animatedRefresher.stateType == .updateData { - self.hasReceivedTxWhileUpdating = true - } - } - - TariEventBus.onBackgroundThread(self, eventType: .txBroadcast) { - [weak self] (_) in - guard let self = self else { return } - if self.animatedRefresher.stateType == .updateData { - self.hasBroadcastTxWhileUpdating = true - } - } - - TariEventBus.onBackgroundThread(self, eventType: .txMined) { - [weak self] (_) in - guard let self = self else { return } - if self.animatedRefresher.stateType == .updateData { - self.hasMinedTxWhileUpdating = true - } - } - - TariEventBus.onBackgroundThread(self, eventType: .txCancellation) { - [weak self] (_) in - guard let self = self else { return } - if self.animatedRefresher.stateType == .updateData { - self.hasCancelledTxWhileUpdating = true - } - } - - TariEventBus.onBackgroundThread(self, eventType: .txValidationSuccessful) { - (_) in - if let wallet = TariLib.shared.tariWallet { - do { - let successful = try wallet.restartTxBroadcast() - TariLogger.info("Restart tx broadcast is successful? \(successful)") - } catch { - TariLogger.error("Error while restarting tx broadcast: \(error.localizedDescription)") - } - } - } - } - - private func startListeningToBaseNodeSync() { - - TariEventBus.onMainThread(self, eventType: .baseNodeSyncComplete) { [weak self] result in - - guard let self = self, let result: [String: Any] = result?.object as? [String: Any] else { return } - guard let isSuccess = result["success"] as? Bool, isSuccess else { - - do { - TariLogger.warn("Base node sync failed or base node not in sync. Setting another random peer.") - try self.switchBaseNode(syncAfterSetting: true) - } catch { - TariLogger.error("Failed to add random base node peer") - } - - // retry sync - self.syncBaseNode() - - return - } - - self.refreshTimeoutTimer?.invalidate() - self.refreshTimeoutTimer = nil - self.animatedRefresher.stateType = .updateData - - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in guard let self = self else { return } - if self.animatedRefresher.stateType != .none { - self.animatedRefresher.playUpdateSequence( - hasReceivedTx: self.hasReceivedTxWhileUpdating, - hasMinedTx: self.hasMinedTxWhileUpdating, - hasBroadcastTx: self.hasBroadcastTxWhileUpdating, - hasCancelledTx: self.hasCancelledTxWhileUpdating - ) { [weak self] in - self?.endRefreshingWithSuccess() - } - } - NotificationManager.shared.cancelAllFutureReminderNotifications() - } - } - } - - private func stopListeningToBaseNodeSync() { - TariEventBus.unregister(self, eventType: .baseNodeSyncComplete) - } - - @objc private func unregisterEvents() { - animatedRefresher.animateOut() - tableView.endRefreshing() - animatedRefresher.stateType = .none - TariEventBus.unregister(self) - } -} - // MARK: setup UI extension TxsListViewController { private func viewSetup() { @@ -713,6 +598,6 @@ extension TxsListViewController { } @objc private func refresh(_ sender: AnyObject) { - beginRefreshing() + syncBaseNode() } } diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift index 78a2abbc..dc8ee532 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/UTXOsWalletModel.swift @@ -153,10 +153,6 @@ final class UTXOsWalletModel { private func breakCoinPreview(breakCount: Int, models: [UtxoModel]) -> BreakPreviewData? { - guard let wallet = TariLib.shared.tariWallet else { - return nil - } - let amount = models .map(\.amount) .reduce(0, +) @@ -164,7 +160,7 @@ final class UTXOsWalletModel { let commitments = models.map(\.commitment) do { - let result = try wallet.previewCoinSplit(commitments: commitments, splitsCount: UInt(breakCount), feePerGram: Wallet.defaultFeePerGram.rawValue) + let result = try Tari.shared.utxos.coinBreakPreview(commitments: commitments, splitsCount: UInt(breakCount)) let breakAmount = (amount - result.fee) / UInt64(breakCount) let amountText = MicroTari(amount).formattedPrecise @@ -193,15 +189,10 @@ final class UTXOsWalletModel { private func performBreakAction(breakCount: Int, models: [UtxoModel]) { - guard let wallet = TariLib.shared.tariWallet else { - errorMessage = ErrorMessageManager.errorModel(forError: nil) - return - } - let commitments = models.map(\.commitment) do { - _ = try wallet.coinSplit(commitments: commitments, splitsCount: UInt(breakCount), feePerGram: Wallet.defaultFeePerGram.rawValue) + try Tari.shared.utxos.breakCoins(commitments: commitments, splitsCount: UInt(breakCount)) } catch { errorMessage = ErrorMessageManager.errorModel(forError: error) } @@ -209,16 +200,12 @@ final class UTXOsWalletModel { func combineCoinsFeePreview() -> String? { - guard let wallet = TariLib.shared.tariWallet else { - return nil - } - let commitments = utxoModels .filter { self.selectedIDs.contains($0.uuid) } .map(\.commitment) do { - let result = try wallet.previewCoinsJoin(commitments: commitments, feePerGram: Wallet.defaultFeePerGram.rawValue) + let result = try Tari.shared.utxos.combineCoinsPreview(commitments: commitments) return MicroTari(result.fee).formattedPrecise } catch { return nil @@ -227,17 +214,12 @@ final class UTXOsWalletModel { func performCombineAction() { - guard let wallet = TariLib.shared.tariWallet else { - errorMessage = ErrorMessageManager.errorModel(forError: nil) - return - } - let commitments = utxoModels .filter { self.selectedIDs.contains($0.uuid) } .map(\.commitment) do { - _ = try wallet.coinsJoin(commitments: commitments, feePerGram: Wallet.defaultFeePerGram.rawValue) + try Tari.shared.utxos.combineCoins(commitments: commitments) } catch { errorMessage = ErrorMessageManager.errorModel(forError: error) } @@ -255,15 +237,10 @@ final class UTXOsWalletModel { private func fetchUTXOs(sortMethod: SortMethod) { - guard let wallet = TariLib.shared.tariWallet else { - errorMessage = ErrorMessageManager.errorModel(forError: nil) - return - } - isLoadingData = true do { - let utxosData = try wallet.allUtxos() + let utxosData = try Tari.shared.utxos.allUtxos .reduce(into: UTXOsData()) { result, model in guard let status = FFIUtxoStatus(rawValue: model.status)?.walletUtxoStatus else { return } @@ -391,8 +368,6 @@ extension FFIUtxoStatus { return nil } } - - var isVisibleUtxoStatus: Bool { walletUtxoStatus != nil } } private struct UTXOsData { diff --git a/MobileWallet/Screens/Home/UTXOs Wallet/Views/ContextualButton.swift b/MobileWallet/Screens/Home/UTXOs Wallet/Views/ContextualButton.swift index e7e20985..0c850d81 100644 --- a/MobileWallet/Screens/Home/UTXOs Wallet/Views/ContextualButton.swift +++ b/MobileWallet/Screens/Home/UTXOs Wallet/Views/ContextualButton.swift @@ -68,8 +68,6 @@ final class ContextualButton: BaseButton { private var labelLeadingConstraint: NSLayoutConstraint? - private var testConst: NSLayoutConstraint? - // MARK: - Initialisers init() { diff --git a/MobileWallet/Screens/Profile/ProfileModel.swift b/MobileWallet/Screens/Profile/ProfileModel.swift index 8e8edb84..b39ff4ff 100644 --- a/MobileWallet/Screens/Profile/ProfileModel.swift +++ b/MobileWallet/Screens/Profile/ProfileModel.swift @@ -64,7 +64,7 @@ final class ProfileModel { @Published private(set) var description: String? @Published private(set) var isReconnectButtonVisible: Bool = false @Published private(set) var qrCodeImage: UIImage? - @Published private(set) var error: MessageModel? + @Published private(set) var errorMessage: MessageModel? @Published private(set) var yatButtonState: YatButtonState = .hidden @Published private(set) var yatPublicKey: String? @@ -83,27 +83,15 @@ final class ProfileModel { // MARK: - Initialisers init() { + updateData() setupCallbacks() } // MARK: - Setups private func setupCallbacks() { - - TariLib.shared.walletStatePublisher - .filter { - switch $0 { - case .started, .startFailed: - return true - case .notReady, .starting: - return false - } - } - .sink { [weak self] _ in self?.updateData() } - .store(in: &cancellables) - $yatButtonState - .sink { [weak self] in self?.updatePresentedData(yatButtonState: $0) } + .sink { [weak self] in try? self?.updatePresentedData(yatButtonState: $0) } .store(in: &cancellables) } @@ -121,22 +109,21 @@ final class ProfileModel { } func reconnectYat() { - yatPublicKey = publicKey?.hex.0 + yatPublicKey = try? publicKey?.byteVector.hex } private func updateData() { - - guard let publicKey = TariLib.shared.tariWallet?.publicKey.0, publicKey.hexDeeplink.1 == nil, let deeplinkData = publicKey.hexDeeplink.0.data(using: .utf8) else { + do { + let walletPublicKey = try Tari.shared.walletPublicKey + let deeplinkData = try walletPublicKey.byteVector.hex.data(using: .utf8) ?? Data() + publicKey = walletPublicKey + qrCodeImage = QRCodeFactory.makeQrCode(data: deeplinkData) + updateYatIdData() + } catch { qrCodeImage = nil emojiData = nil - error = MessageModel(title: localized("profile_view.error.qr_code.title"), message: localized("wallet.error.failed_to_access"), type: .error) - return + errorMessage = MessageModel(title: localized("profile_view.error.qr_code.title"), message: localized("wallet.error.failed_to_access"), type: .error) } - - self.publicKey = publicKey - qrCodeImage = QRCodeFactory.makeQrCode(data: deeplinkData) - - updateYatIdData() } func updateYatIdData() { @@ -165,7 +152,7 @@ final class ProfileModel { return } - isYatOutOfSync = walletAddress != publicKey?.hex.0 + isYatOutOfSync = walletAddress != (try? publicKey?.byteVector.hex) } private func handle(completion: Subscribers.Completion) { @@ -179,14 +166,14 @@ final class ProfileModel { private func show(error: Error?) { yatButtonState = .hidden - self.error = ErrorMessageManager.errorModel(forError: error) + self.errorMessage = ErrorMessageManager.errorModel(forError: error) } - private func updatePresentedData(yatButtonState: YatButtonState) { + private func updatePresentedData(yatButtonState: YatButtonState) throws { switch yatButtonState { case .hidden, .loading, .off: guard let publicKey = publicKey else { return } - emojiData = EmojiData(emojiID: publicKey.emojis.0, hex: publicKey.hex.0, copyText: localized("emoji.copy"), tooltipText: localized("emoji.hex_tip")) + emojiData = EmojiData(emojiID: try publicKey.emojis, hex: try publicKey.byteVector.hex, copyText: localized("emoji.copy"), tooltipText: localized("emoji.hex_tip")) description = walletDescription isReconnectButtonVisible = false case .on: diff --git a/MobileWallet/Screens/Profile/ProfileViewController.swift b/MobileWallet/Screens/Profile/ProfileViewController.swift index fad89430..98495c6e 100644 --- a/MobileWallet/Screens/Profile/ProfileViewController.swift +++ b/MobileWallet/Screens/Profile/ProfileViewController.swift @@ -60,7 +60,6 @@ final class ProfileViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupBindings() - Tracker.shared.track("/home/profile", "Profile - Wallet Info") } override func viewWillAppear(_ animated: Bool) { @@ -74,41 +73,41 @@ final class ProfileViewController: UIViewController { model.$qrCodeImage .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.qrCodeImage, on: mainView) .store(in: &cancellables) model.$emojiData .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.mainView.update(emojiID: $0.emojiID, hex: $0.hex, copyText: $0.copyText, tooltopText: $0.tooltipText) } .store(in: &cancellables) model.$description - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.text, on: mainView.middleLabel) .store(in: &cancellables) model.$isReconnectButtonVisible .map { !$0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.isHidden, on: mainView.reconnectYatButton) .store(in: &cancellables) - model.$error + model.$errorMessage .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.show(error: $0) } .store(in: &cancellables) model.$yatButtonState - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.handle(yatButtonState: $0) } .store(in: &cancellables) model.$yatPublicKey .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.showYatOnboardingFlow(publicKey: $0) } .store(in: &cancellables) diff --git a/MobileWallet/Screens/RestoreWallet/RestoreWalletViewController.swift b/MobileWallet/Screens/RestoreWallet/RestoreWalletViewController.swift index b6f27d3f..64d3328e 100644 --- a/MobileWallet/Screens/RestoreWallet/RestoreWalletViewController.swift +++ b/MobileWallet/Screens/RestoreWallet/RestoreWalletViewController.swift @@ -147,7 +147,6 @@ extension RestoreWalletViewController: UITableViewDelegate, UITableViewDataSourc return } - TariLib.shared.startWallet(seedWords: nil) self?.pendingView.hidePendingView { [weak self] in self?.returnToSplashScreen() } @@ -156,15 +155,7 @@ extension RestoreWalletViewController: UITableViewDelegate, UITableViewDataSourc } private func returnToSplashScreen() { - let navigationController = AlwaysPoppableNavigationController( - rootViewController: SplashViewController() - ) - navigationController.setNavigationBarHidden( - true, - animated: false - ) - UIApplication.shared.windows.first?.rootViewController = navigationController - UIApplication.shared.windows.first?.makeKeyAndVisible() + AppRouter.transitionToSplashScreen() } } diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift index cdc08bb8..ac75792e 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsModel.swift @@ -126,12 +126,11 @@ final class RestoreWalletFromSeedsModel { .map { $0.title } do { - let walletSeedWords = try SeedWords(words: seedWords) - try TariLib.shared.createNewWallet(seedWords: walletSeedWords) + try Tari.shared.restoreWallet(seedWords: seedWords) viewModel.isEmptyWalletCreated = true - } catch let error as SeedWords.Error { + } catch let error as SeedWords.InternalError { handle(seedWordsError: error) - } catch let error as WalletErrors { + } catch let error as WalletError { handle(walletError: error) } catch { handleUnknownError() @@ -158,7 +157,8 @@ final class RestoreWalletFromSeedsModel { } private func fetchAvailableSeedWords() { - availableAutocompletionTokens = (try? SeedWordsMnemonicWordList(language: .english)?.seedWords.map { TokenViewModel(id: UUID(), title: $0) }) ?? [] + let seedWords = (try? Tari.shared.recovery.allSeedWords(forLanguage: .english)) ?? [] + availableAutocompletionTokens = seedWords.map { TokenViewModel(id: UUID(), title: $0) } } private func appendModelsBeforeEditingModel(models: [SeedWordModel]) { @@ -181,20 +181,17 @@ final class RestoreWalletFromSeedsModel { viewModel.updatedInputText = lastToken } - private func handle(seedWordsError: SeedWords.Error) { + private func handle(seedWordsError: SeedWords.InternalError) { viewModel.error = ErrorMessageManager.errorModel(forError: seedWordsError) } - private func handle(walletError: WalletErrors) { + private func handle(walletError: WalletError) { - guard let description = walletError.errorDescription else { - handleUnknownError() - return - } + let message = ErrorMessageManager.errorMessage(forError: walletError) viewModel.error = MessageModel( title: localized("restore_from_seed_words.error.title"), - message: description, + message: message, type: .error ) } diff --git a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift index 81991ad6..546c2951 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeeds/RestoreWalletFromSeedsViewController.swift @@ -118,12 +118,12 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, .store(in: &cancelables) model.viewModel.$seedWordModels - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.seedWords, on: mainView.tokenView) .store(in: &cancelables) mainView.tokenView.$inputText - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .assign(to: \.inputText, on: model) .store(in: &cancelables) @@ -155,9 +155,8 @@ final class RestoreWalletFromSeedsViewController: SettingsParentViewController, let overlay = RestoreWalletFromSeedsProgressViewController() - overlay.onSuccess = { [weak self, weak overlay] in - overlay?.dismiss(animated: true) - self?.navigationController?.popToRootViewController(animated: true) + overlay.onSuccess = { + AppRouter.transitionToSplashScreen() } show(overlay: overlay) diff --git a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/RestoreWalletFromSeedsProgressModel.swift b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/RestoreWalletFromSeedsProgressModel.swift index e56e65dc..37846ef8 100644 --- a/MobileWallet/Screens/RestoreWalletFromSeedsProgress/RestoreWalletFromSeedsProgressModel.swift +++ b/MobileWallet/Screens/RestoreWalletFromSeedsProgress/RestoreWalletFromSeedsProgressModel.swift @@ -53,7 +53,7 @@ final class RestoreWalletFromSeedsProgressModel { // MARK: - Properties let viewModel = ViewModel() - private var cancelables: Set = [] + private var cancellables: Set = [] // MARK: - Initializers @@ -67,40 +67,28 @@ final class RestoreWalletFromSeedsProgressModel { NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) .sink { [weak self] _ in - self?.resumeRestoringWallet() + self?.startRestoringWallet() } - .store(in: &cancelables) + .store(in: &cancellables) - TariEventBus - .events(forType: .restoreWalletStatusUpdate) - .compactMap { $0.object as? RestoreWalletStatus } + WalletCallbacksManager.shared.walletRecoveryStatusUpdate + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.handle(restoreStatus: $0) } - .store(in: &cancelables) + .store(in: &cancellables) } // MARK: - Actions func startRestoringWallet() { do { - let result = try TariLib.shared.tariWallet?.startRecovery() - if result == false { + let isSuccess = try Tari.shared.recovery.startRecovery(recoveredOutputMessage: localized("transaction.one_sided_payment.note.recovered")) + if !isSuccess { handleStartRecoveryFailure() } } catch { handleStartRecoveryFailure() } } - - private func resumeRestoringWallet() { - WalletConnectivityManager.startWallet { [weak self] result in - switch result { - case .success: - self?.startRestoringWallet() - case .failure: - self?.handleStartRecoveryFailure() - } - } - } // MARK: - Handlers diff --git a/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift b/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift index a0808314..a8424492 100644 --- a/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift +++ b/MobileWallet/Screens/Send/AddAmount/AddAmountViewController.swift @@ -176,8 +176,6 @@ final class AddAmountViewController: UIViewController { let tariAmount = MicroTari(amount) addCharacter(tariAmount.formattedPrecise) } - - Tracker.shared.track("/home/send_tari/add_amount", "Send Tari - Add Amount") setupOneSidedPaymentElements() setupSliderBar() @@ -191,24 +189,14 @@ final class AddAmountViewController: UIViewController { } private func displayAliasOrEmojiId() { - guard let wallet = TariLib.shared.tariWallet else { - return - } - do { - guard let contact = try wallet.contacts.0?.find(publicKey: paymentInfo.publicKey) else { return } - - if contact.alias.0.trimmingCharacters(in: .whitespaces).isEmpty { - try navigationBar.showEmojiId(paymentInfo.publicKey, inViewController: self) - } else { - navigationBar.title = contact.alias.0 + guard let contact = try Tari.shared.contacts.findContact(hex: paymentInfo.publicKey.byteVector.hex) else { + navigationBar.showEmojiId(emojiID: try paymentInfo.publicKey.emojis, hex: try paymentInfo.publicKey.byteVector.hex, presenterController: self) + return } + navigationBar.title = try contact.alias } catch { - do { - try navigationBar.showEmojiId(paymentInfo.publicKey, inViewController: self) - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("navigation_bar.error.show_emoji.title"), message: localized("navigation_bar.error.show_emoji.description"), type: .error)) - } + PopUpPresenter.show(message: MessageModel(title: localized("navigation_bar.error.show_emoji.title"), message: localized("navigation_bar.error.show_emoji.description"), type: .error)) } } @@ -289,7 +277,7 @@ final class AddAmountViewController: UIViewController { return } - if numberOfDecimals(in: updatedText) > MicroTari.MAX_FRACTION_DIGITS { + if numberOfDecimals(in: updatedText) > MicroTari.maxFractionDigits { return } @@ -350,7 +338,7 @@ final class AddAmountViewController: UIViewController { return false } - guard numberOfDecimals(in: string) <= MicroTari.MAX_FRACTION_DIGITS else { + guard numberOfDecimals(in: string) <= MicroTari.maxFractionDigits else { return false } @@ -421,12 +409,12 @@ final class AddAmountViewController: UIViewController { private func showAvailableBalance() { - guard let totalBalance = try? TariLib.shared.tariWallet?.totalBalance else { return } + let totalBalance = Tari.shared.walletBalance.balance.total walletBalanceStackView.isHidden = false warningView.isHidden = false warningView.layer.borderWidth = 0 - walletBalanceLabel.text = totalBalance.formatted + walletBalanceLabel.text = MicroTari(totalBalance).formatted balanceExceededLabel.isHidden = true balancePendingLabel.isHidden = true walletBalanceTitleLabel.isHidden = false @@ -467,45 +455,34 @@ final class AddAmountViewController: UIViewController { private func calculateAmount() -> MicroTari? { - guard let wallet = TariLib.shared.tariWallet else { return nil } + let availableBalance = Tari.shared.walletBalance.balance.available + var tariAmount: MicroTari? do { - let availableBalance = try wallet.balance().available - - var tariAmount: MicroTari? - do { - tariAmount = try MicroTari(tariValue: rawInput) - } catch { - showInvalidNumberError(error) - } - - guard let amount = tariAmount else { return nil } - - var fee = MicroTari(0) - do { - fee = try wallet.estimateTxFee( - amount: amount, - feePerGram: Wallet.defaultFeePerGram, - kernelCount: Wallet.defaultKernelCount, - outputCount: Wallet.defaultOutputCount - ) - } catch { - return nil - } - - if amount.rawValue + fee.rawValue > availableBalance { - PopUpPresenter.show(message: MessageModel( - title: localized("add_amount.info.wait_completion_previous_tx.title"), - message: localized("add_amount.info.wait_completion_previous_tx.description"), - type: .normal - )) - return nil - } - - return amount + tariAmount = try MicroTari(tariValue: rawInput) + } catch { + showInvalidNumberError(error) + } + + guard let amount = tariAmount else { return nil } + + let fee: UInt64 + do { + fee = try Tari.shared.fees.estimateFee(amount: amount.rawValue) } catch { return nil } + + if amount.rawValue + fee > availableBalance { + PopUpPresenter.show(message: MessageModel( + title: localized("add_amount.info.wait_completion_previous_tx.title"), + message: localized("add_amount.info.wait_completion_previous_tx.description"), + type: .normal + )) + return nil + } + + return amount } private func updateNextStepElements(isEnabled: Bool) { @@ -904,14 +881,14 @@ extension AddAmountViewController { private func handle(error: Error) { switch error { - case WalletErrors.notEnoughFunds: + case WalletError.notEnoughFunds: guard let totalBalance = fetchTotalBalance() else { return } balanceExceededLabel.isHidden = false balancePendingLabel.isHidden = true showBalanceExceeded(balance: totalBalance.formatted) walletBalanceStackView.isHidden = false updateNextStepElements(isEnabled: false) - case WalletErrors.fundsPending: + case WalletError.fundsPending: guard let totalBalance = fetchTotalBalance() else { return } balanceExceededLabel.isHidden = true balancePendingLabel.isHidden = false @@ -942,15 +919,8 @@ extension AddAmountViewController { } private func fetchTotalBalance() -> MicroTari? { - - guard let wallet = TariLib.shared.tariWallet else { return nil } - - do { - return try wallet.totalBalance - } catch { - PopUpPresenter.show(message: MessageModel(title: localized("add_amount.error.available_balance.title"), message: localized("add_amount.error.available_balance.description"), type: .error)) - return nil - } + let totalBalance = Tari.shared.walletBalance.balance.total + return MicroTari(totalBalance) } } diff --git a/MobileWallet/Screens/Send/AddAmount/TransactionFeesManager.swift b/MobileWallet/Screens/Send/AddAmount/TransactionFeesManager.swift index 3508a724..2892b52f 100644 --- a/MobileWallet/Screens/Send/AddAmount/TransactionFeesManager.swift +++ b/MobileWallet/Screens/Send/AddAmount/TransactionFeesManager.swift @@ -97,9 +97,7 @@ final class TransactionFeesManager { private func updateData() { - guard let wallet = TariLib.shared.tariWallet else { return } - - fetchTrafficAndFeesPerGram(wallet: wallet) { [weak self] result in + fetchTrafficAndFeesPerGram { [weak self] result in guard let self = self else { return } @@ -117,10 +115,8 @@ final class TransactionFeesManager { private func updateFees(networkTraffic: NetworkTraffic, feesPerGram: FeeOptions) { - guard let wallet = TariLib.shared.tariWallet else { return } - do { - let fees = try calculateFees(wallet: wallet, amount: amount, feesPerGram: feesPerGram) + let fees = try calculateFees(amount: amount, feesPerGram: feesPerGram) let feesData = FeesData(networkTraffic: networkTraffic, feesPerGram: feesPerGram, fees: fees) self.feesData = feesData feesStatus = .data(feesData) @@ -138,7 +134,7 @@ final class TransactionFeesManager { updateFees(networkTraffic: networkTraffic, feesPerGram: feesPerGram) } - private func fetchTrafficAndFeesPerGram(wallet: Wallet, result: @escaping (Result<(NetworkTraffic, FeeOptions), Error>) -> Void) { + private func fetchTrafficAndFeesPerGram(result: @escaping (Result<(NetworkTraffic, FeeOptions), Error>) -> Void) { DispatchQueue.global().async { [weak self] in @@ -150,21 +146,21 @@ final class TransactionFeesManager { var response: (NetworkTraffic, FeeOptions)? DispatchQueue.global().async { - response = try? self.calculateTrafficAndFeesPerGram(wallet: wallet) + response = try? self.calculateTrafficAndFeesPerGram() dispatchGroup.leave() } _ = dispatchGroup.wait(timeout: .now() + self.timeout) - let finalResponse = response ?? (.unknown, FeeOptions(slow: Wallet.defaultFeePerGram, medium: Wallet.defaultFeePerGram, fast: Wallet.defaultFeePerGram)) + let finalResponse = response ?? (.unknown, FeeOptions(slow: Tari.defaultFeePerGram, medium: Tari.defaultFeePerGram, fast: Tari.defaultFeePerGram)) result(.success(finalResponse)) } } - private func calculateTrafficAndFeesPerGram(wallet: Wallet) throws -> (NetworkTraffic, FeeOptions) { + private func calculateTrafficAndFeesPerGram() throws -> (NetworkTraffic, FeeOptions) { - let stats = try TariFeePerGramStats(walletPointer: wallet.pointer, count: 3) - let blocksCount = try stats.count() + let stats = try Tari.shared.fees.feePerGramStats(count: 3) + let blocksCount = try stats.count let elementsCount = min(blocksCount, 3) let elements = try (0.. FeeOptions { + private func calculateFees(amount: MicroTari, feesPerGram: FeeOptions) throws -> FeeOptions { - let maxAmountRaw = try wallet.totalBalance.rawValue - rawMaxAmountBuffer - let amountRaw = min(amount.rawValue, maxAmountRaw) - let amount = MicroTari(amountRaw) + let totalBalance = Tari.shared.walletBalance.balance.total + let maxAmountRaw = totalBalance > rawMaxAmountBuffer ? totalBalance - rawMaxAmountBuffer : 0 + let amount = min(amount.rawValue, maxAmountRaw) - let slowOption = try wallet.estimateTxFee(amount: amount, feePerGram: feesPerGram.slow, kernelCount: Wallet.defaultKernelCount, outputCount: Wallet.defaultOutputCount) - let mediumOption = try wallet.estimateTxFee(amount: amount, feePerGram: feesPerGram.medium, kernelCount: Wallet.defaultKernelCount, outputCount: Wallet.defaultOutputCount) - let fastOption = try wallet.estimateTxFee(amount: amount, feePerGram: feesPerGram.fast, kernelCount: Wallet.defaultKernelCount, outputCount: Wallet.defaultOutputCount) + let slowOption = try Tari.shared.fees.estimateFee(amount: amount, feePerGram: feesPerGram.slow.rawValue) + let mediumOption = try Tari.shared.fees.estimateFee(amount: amount, feePerGram: feesPerGram.medium.rawValue) + let fastOption = try Tari.shared.fees.estimateFee(amount: amount, feePerGram: feesPerGram.fast.rawValue) - return FeeOptions(slow: slowOption, medium: mediumOption, fast: fastOption) + return FeeOptions(slow: MicroTari(slowOption), medium: MicroTari(mediumOption), fast: MicroTari(fastOption)) } } diff --git a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift index 3cd2cc20..7cbeb2c0 100644 --- a/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift +++ b/MobileWallet/Screens/Send/AddNote/AddNoteViewController.swift @@ -122,7 +122,6 @@ final class AddNoteViewController: UIViewController, GiphyDelegate, GPHGridDeleg setup() hideKeyboardWhenTappedAroundOrSwipedDown(view: attachmentContainer) displayAliasOrEmojiId() - Tracker.shared.track("/home/send_tari/add_note", "Send Tari - Add Note") } override func viewDidAppear(_ animated: Bool) { @@ -147,24 +146,24 @@ final class AddNoteViewController: UIViewController, GiphyDelegate, GPHGridDeleg } private func displayAliasOrEmojiId() { - guard let wallet = TariLib.shared.tariWallet else { - return - } - + + var alias: String? + do { - guard let contact = try wallet.contacts.0?.find(publicKey: paymentInfo.publicKey) else { return } - if contact.alias.0.trimmingCharacters(in: .whitespaces).isEmpty { - try navigationBar.showEmojiId(paymentInfo.publicKey, inViewController: self) - } else { - navigationBar.title = contact.alias.0 - } + alias = try Tari.shared.contacts.findContact(hex: try paymentInfo.publicKey.byteVector.hex)?.alias } catch { + } + + guard let alias = alias, !alias.trimmingCharacters(in: .whitespaces).isEmpty else { do { - try navigationBar.showEmojiId(paymentInfo.publicKey, inViewController: self) + try navigationBar.showEmojiId(emojiID: paymentInfo.publicKey.emojis, hex: paymentInfo.publicKey.byteVector.hex, presenterController: self) } catch { PopUpPresenter.show(message: MessageModel(title: localized("navigation_bar.error.show_emoji.title"), message: localized("navigation_bar.error.show_emoji.description"), type: .error)) } + return } + + navigationBar.title = alias } func updateTitleColorAndSetSendButtonState() { @@ -286,22 +285,10 @@ final class AddNoteViewController: UIViewController, GiphyDelegate, GPHGridDeleg private func onSlideToEndAction() { dismissKeyboard() - - Tracker.shared.track( - eventWithCategory: "Transaction", - action: "Transaction Initiated" - ) - - guard let wallet = TariLib.shared.tariWallet else { - PopUpPresenter.show(message: MessageModel(title: localized("wallet.error.title"), message: localized("wallet.error.wallet_not_initialized"), type: .error)) - sendButton.resetStateWithAnimation(true) - return - } - - sendTx(wallet, recipientPublicKey: paymentInfo.publicKey, amount: amount, feePerGram: feePerGram) + sendTx(recipientPublicKey: paymentInfo.publicKey, amount: amount, feePerGram: feePerGram) } - private func sendTx(_ wallet: Wallet, recipientPublicKey: PublicKey, amount: MicroTari, feePerGram: MicroTari) { + private func sendTx(recipientPublicKey: PublicKey, amount: MicroTari, feePerGram: MicroTari) { var message = noteText @@ -521,7 +508,7 @@ extension AddNoteViewController: UITextViewDelegate { // Limit to the size of a tx note let charLimit = 280 if trimmedText.count > charLimit { - TariLogger.warn("Limitting tx note to \(charLimit) chars") + Logger.log(message: "Limitting tx note to \(charLimit) chars", domain: .general, level: .warning) trimmedText = String(trimmedText.prefix(charLimit)) textView.text = trimmedText } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift index 4420d967..ce88e57b 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientModel.swift @@ -86,7 +86,7 @@ final class AddRecipientModel { // MARK: - Properties - private var publicKey: PublicKey? + @Published private var publicKey: PublicKey? private var contacts: [UUID: Contact] = [:] private var recentPublicKeys: [PublicKeyMetadata] = [] private var allRecentContactsItems: [ContactElementItem] = [] @@ -109,9 +109,14 @@ final class AddRecipientModel { .store(in: &cancelables) searchText - .throttle(for: .milliseconds(750), scheduler: RunLoop.main, latest: true) + .throttle(for: .milliseconds(750), scheduler: DispatchQueue.main, latest: true) .sink { [weak self] in self?.searchAddress(forYatID: $0) } .store(in: &cancelables) + + $publicKey + .map { $0 != nil } + .assign(to: \.canMoveToNextStep, on: self) + .store(in: &cancelables) } // MARK: - Actions - View Model @@ -130,14 +135,13 @@ final class AddRecipientModel { func onSelectItem(atIndexPath indexPath: IndexPath) { let publicKey: PublicKey? - let model = contactsSectionItems[indexPath.section].items[indexPath.row] switch indexPath.section { case 0: - publicKey = recentPublicKeys.first(where: { $0.uuid == model.id } )?.publicKey + publicKey = recentPublicKeys.first { $0.uuid == model.id }?.publicKey case 1: - publicKey = contacts[model.id]?.publicKey.0 + publicKey = try? contacts[model.id]?.publicKey default: return } @@ -152,64 +156,50 @@ final class AddRecipientModel { func toogleYatPreview() { let isAddressVisible = walletAddressPreview != nil - walletAddressPreview = isAddressVisible ? nil : publicKey?.hex.0 + walletAddressPreview = isAddressVisible ? nil : try? publicKey?.byteVector.hex } // MARK: - Actions - Contacts private func fetchWalletContacts() { - TariLib.shared.walletStatePublisher - .sink { [weak self] in self?.handle(walletState: $0) } - .store(in: &cancelables) - } - - private func handle(walletState: TariLib.WalletState) { - switch walletState { - case .started: - do { - try loadContacts() - } catch { - errorDialog = MessageModel(title: localized("add_recipient.error.load_contacts.title"), message: localized("add_recipient.error.load_contacts.description"), type: .error) - } - case .startFailed: - errorDialog = MessageModel(title: localized("add_recipient.error.load_contacts.title"), message: localized("add_recipient.error.load_contacts.description"), type: .error) - case .notReady, .starting: - break - } - } - - private func loadContacts() throws { - - guard let wallet = TariLib.shared.tariWallet else { - throw WalletErrors.walletNotInitialized - } - let (walletContacts, contactsError) = wallet.contacts + let sortedContacts: [Contact] - if let contactsError = contactsError { - throw contactsError + do { + sortedContacts = try Tari.shared.contacts.allContacts.sorted { try $0.alias.lowercased() < $1.alias.lowercased() } + } catch { + presentFetchContactsError() + return } - guard let walletContacts = walletContacts else { - throw WalletErrors.walletNotInitialized - } - let (contactList, listError) = walletContacts.list + contacts = sortedContacts.reduce(into: [UUID: Contact]()) { $0[UUID()] = $1 } - if let listError = listError { - throw listError + do { + recentPublicKeys = try fetchRecentPublicKeys().map { PublicKeyMetadata(uuid: UUID(), publicKey: $0) } + allRecentContactsItems = try recentPublicKeys.map { try Tari.shared.contacts.findContact(hex: $0.publicKey.byteVector.hex)?.viewItem(uuid: $0.uuid) ?? $0.publicKey.viewItem(uuid: $0.uuid) } + allContactsItems = try contacts.map { try $1.viewItem(uuid: $0) } + } catch { + presentFetchContactsError() } - let sortedContacts = contactList.sorted { $0.alias.0.lowercased() < $1.alias.0.lowercased() } - let publicKeys = try wallet.recentPublicKeys(limit: 3) + filterContacts(searchText: "") + } + + private func fetchRecentPublicKeys() throws -> [PublicKey] { - recentPublicKeys = publicKeys.map { PublicKeyMetadata(uuid: UUID(), publicKey: $0) } - contacts = sortedContacts.reduce(into: [UUID: Contact]()) { $0[UUID()] = $1 } + let transactions: [Transaction] = Tari.shared.transactions.completed + Tari.shared.transactions.pendingInbound + Tari.shared.transactions.pendingOutbound - allRecentContactsItems = recentPublicKeys.map { (try? TariLib.shared.tariWallet?.contacts.0?.find(publicKey: $0.publicKey).viewItem(uuid: $0.uuid)) ?? $0.publicKey.viewItem(uuid: $0.uuid) } - allContactsItems = contacts.map { $1.viewItem(uuid: $0) }.sorted { $0.title < $1.title } + let recentTransactions = try transactions + .sorted { try $0.timestamp > $1.timestamp } + .map { try $0.publicKey } + .reduce(into: [PublicKey]()) { result, publicKey in + guard !result.contains(publicKey) else { return } + result.append(publicKey) + } + .prefix(3) - filterContacts(searchText: "") + return Array(recentTransactions) } private func searchForContact(searchText: String) { @@ -227,18 +217,23 @@ final class AddRecipientModel { recentContactsItems = allRecentContactsItems contactsItems = allContactsItems } else { - let contactList = TariLib.shared.tariWallet?.contacts.0?.list.0 + let walletContacts = try? Tari.shared.contacts.allContacts + recentContactsItems = recentPublicKeys - .filter { - dataPair in dataPair.publicKey.emojis.0.localizedCaseInsensitiveContains(searchText) || - ((contactList?.filter { $0.publicKey.0 == dataPair.publicKey }.first?.alias.0.localizedCaseInsensitiveContains(searchText)) == true) } + .filter { publicKeyMetadata in + (try? publicKeyMetadata.publicKey.emojis.localizedStandardContains(searchText)) == true + || (try? walletContacts?.filter { (try? $0.publicKey) == publicKeyMetadata.publicKey }.first?.alias.localizedStandardContains(searchText)) == true + } .map(\.uuid) - .compactMap { uuid in allRecentContactsItems.first { $0.id == uuid } } + .compactMap { uuid in allRecentContactsItems.first { $0.id == uuid }} contactsItems = contacts - .filter { $0.value.alias.0.localizedCaseInsensitiveContains(searchText) } + .filter { + guard let alias = try? $0.value.alias else { return false } + return alias.localizedCaseInsensitiveContains(searchText) + } .map(\.key) - .compactMap { uuid in allContactsItems.first { $0.id == uuid } } + .compactMap { uuid in allContactsItems.first { $0.id == uuid }} } if !recentContactsItems.isEmpty { @@ -252,6 +247,10 @@ final class AddRecipientModel { self.contactsSectionItems = contactsSectionItems } + private func presentFetchContactsError() { + errorDialog = MessageModel(title: localized("add_recipient.error.load_contacts.title"), message: localized("add_recipient.error.load_contacts.description"), type: .error) + } + // MARK: - Actions - Yat private func searchAddress(forYatID yatID: String) { @@ -276,55 +275,40 @@ final class AddRecipientModel { // MARK: - Actions - Public Key private func generatePublicKey(text: String) { - - do { - let publicKey = try PublicKey(any: text) - verify(publicKey: publicKey) - self.publicKey = publicKey - } catch { + guard let publicKey = try? makePublicKey(text: text), verify(publicKey: publicKey) else { publicKey = nil - handle(error: error) + return } - - canMoveToNextStep = publicKey != nil + self.publicKey = publicKey } - private func verify(publicKey: PublicKey) { - - guard publicKey.hex.0 != TariLib.shared.tariWallet?.publicKey.0?.hex.0 else { - errorMessage = localized("add_recipient.warning.can_not_send_yourself.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) - return - } + private func makePublicKey(text: String) throws -> PublicKey { + do { return try PublicKey(emojiID: text) } catch {} + return try PublicKey(hex: text) } - private func handle(error: Error) { - - guard let error = error as? PublicKeyError else { - errorMessage = nil - return - } - - switch error { - case .invalidEmojiSet: - errorMessage = localized("add_recipient.error.load_contacts.invalid_emoji_set") - default: - errorMessage = nil + private func verify(publicKey: PublicKey) -> Bool { + guard let hex = try? publicKey.byteVector.hex, let userHex = try? Tari.shared.walletPublicKey.byteVector.hex, hex != userHex else { + errorMessage = localized("add_recipient.warning.can_not_send_yourself.with_param", arguments: NetworkManager.shared.selectedNetwork.tickerSymbol) + return false } + return true } // MARK: - Actions - Pasteboard func checkPasteboard() { - guard let pasteboardText = UIPasteboard.general.string, let publicKey = try? PublicKey(any: pasteboardText) else { return } - validatedPasteboardText = publicKey.emojis.0 + guard let pasteboardText = UIPasteboard.general.string, let emojis = try? makePublicKey(text: pasteboardText).emojis else { return } + validatedPasteboardText = emojis } } private extension Contact { - func viewItem(uuid: UUID) -> ContactElementItem { - let isEmojiID = alias.0.isEmpty - let title = isEmojiID ? (publicKey.0?.emojis.0.obfuscatedText ?? "") : alias.0 + func viewItem(uuid: UUID) throws -> ContactElementItem { + let alias = try alias + let isEmojiID = alias.isEmpty + let title = isEmojiID ? try publicKey.emojis.obfuscatedText : alias let initial = isEmojiID ? "" : title.prefix(1) return ContactElementItem(id: uuid, title: title, initial: String(initial), isEmojiID: isEmojiID) } @@ -333,7 +317,7 @@ private extension Contact { private extension PublicKey { func viewItem(uuid: UUID) -> ContactElementItem { - let title = emojis.0.obfuscatedText + let title = (try? emojis.obfuscatedText) ?? "" return ContactElementItem(id: uuid, title: title, initial: "", isEmojiID: true) } } diff --git a/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift b/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift index 267618ce..faa3b81f 100644 --- a/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift +++ b/MobileWallet/Screens/Send/AddRecipient/AddRecipientViewController.swift @@ -67,7 +67,6 @@ final class AddRecipientViewController: UIViewController { setupViews() setupFeedbacks() hideKeyboardWhenTappedAroundOrSwipedDown() - Tracker.shared.track("/home/send_tari/add_recipient", "Send Tari - Add Recipient") } override func viewWillAppear(_ animated: Bool) { @@ -222,8 +221,8 @@ final class AddRecipientViewController: UIViewController { } private func handleDeeplink() { - guard let deeplink = deeplink, let publicKey = try? PublicKey(hex: deeplink.receiverPublicKey) else { return } - model.searchText.send(publicKey.emojis.0) + guard let deeplink = deeplink, let emojiID = try? PublicKey(hex: deeplink.receiverPublicKey).emojis else { return } + model.searchText.send(emojiID) } } diff --git a/MobileWallet/Screens/Send/AddRecipient/ScanViewController.swift b/MobileWallet/Screens/Send/AddRecipient/ScanViewController.swift index 3572ce26..df9afe64 100644 --- a/MobileWallet/Screens/Send/AddRecipient/ScanViewController.swift +++ b/MobileWallet/Screens/Send/AddRecipient/ScanViewController.swift @@ -99,10 +99,6 @@ class ScanViewController: UIViewController { updateConstraintsBottomLeftView() updateConstraintsBottomRightView() setupScanner() - - if scanResourceType == .publicKey { - Tracker.shared.track("/home/send_tari/add_recipient/qr_scan", "Send Tari - Add Recipient - Scan QR Code") - } } private func updateConstraintsBackButton() { diff --git a/MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift b/MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift index 286bd699..793e4dbb 100644 --- a/MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift +++ b/MobileWallet/Screens/Send/AddRecipient/Views/ErrorView.swift @@ -51,7 +51,6 @@ final class ErrorView: UIView { } else { alpha = 1.0 UINotificationFeedbackGenerator().notificationOccurred(.error) - TariLogger.error(message) } } diff --git a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift index 95ea8b5a..0c48a7c0 100644 --- a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift +++ b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountModel.swift @@ -112,7 +112,7 @@ final class RequestTariAmountModel { // MARK: - Factories private func makeDeeplink() -> URL? { - guard let publicKey = TariLib.shared.tariWallet?.publicKey.0?.hex.0, let tariAmount = try? MicroTari(tariValue: amountFormatter.amount) else { return nil } + guard let publicKey = try? Tari.shared.walletPublicKey.byteVector.hex, let tariAmount = try? MicroTari(tariValue: amountFormatter.amount) else { return nil } let model = TransactionsSendDeeplink(receiverPublicKey: publicKey, amount: tariAmount.rawValue, note: nil) return try? DeepLinkFormatter.deeplink(model: model) } diff --git a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountViewController.swift b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountViewController.swift index 04afd348..9e0ccaac 100644 --- a/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountViewController.swift +++ b/MobileWallet/Screens/Send/New/RequestTari/RequestTariAmountViewController.swift @@ -75,13 +75,13 @@ final class RequestTariAmountViewController: UIViewController { model.$qrCode .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.showQrCode(image: $0) } .store(in: &cancellables) model.$deeplink .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.showShareDialog(data: $0) } .store(in: &cancellables) @@ -123,4 +123,4 @@ final class RequestTariAmountViewController: UIViewController { controller.popoverPresentationController?.sourceView = mainView.shareButton present(controller, animated: true) } -} \ No newline at end of file +} diff --git a/MobileWallet/Screens/Send/SendingTari/SendingTariModel.swift b/MobileWallet/Screens/Send/SendingTari/SendingTariModel.swift index 9eae3b19..e230ff72 100644 --- a/MobileWallet/Screens/Send/SendingTari/SendingTariModel.swift +++ b/MobileWallet/Screens/Send/SendingTari/SendingTariModel.swift @@ -94,17 +94,10 @@ final class SendingTariModel { // MARK: - Setups private func setupCallbacks() { - - if #available(iOS 14.0, *) { - $state - .compactMap { [weak self] in self?.stateModel(forState:$0) } - .assign(to: &$stateModel) - } else { - $state - .compactMap { [weak self] in self?.stateModel(forState:$0) } - .assign(to: \.stateModel, on: self) - .store(in: &cancellables) - } + $state + .compactMap { [weak self] in self?.stateModel(forState:$0) } + .assign(to: \.stateModel, on: self) + .store(in: &cancellables) } // MARK: - Actions diff --git a/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift b/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift index ab0f1b4d..d38d3c9e 100644 --- a/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift +++ b/MobileWallet/Screens/Send/SendingTari/SendingTariViewController.swift @@ -74,7 +74,6 @@ final class SendingTariViewController: UIViewController, TransactionViewControll super.viewDidLoad() runBackgroundAnimation() setupBindings() - Tracker.shared.track("/home/send_tari/finalize", "Send Tari - Finalize") } override func viewDidAppear(_ animated: Bool) { @@ -95,7 +94,7 @@ final class SendingTariViewController: UIViewController, TransactionViewControll model.$stateModel .compactMap { $0 } - .receive(on: RunLoop.main) + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateViews(model: $0) } .store(in: &cancelables) diff --git a/MobileWallet/Screens/Send/YatTransaction/Managers/YatCacheManager.swift b/MobileWallet/Screens/Send/YatTransaction/Managers/YatCacheManager.swift index c34c0044..ef18d2ee 100644 --- a/MobileWallet/Screens/Send/YatTransaction/Managers/YatCacheManager.swift +++ b/MobileWallet/Screens/Send/YatTransaction/Managers/YatCacheManager.swift @@ -92,7 +92,7 @@ final class YatCacheManager { try data.write(to: fileURL) return FileData(url: fileURL, identifier: components.identifier) } catch { - TariLogger.error("Unable to cache Yat Visualisation", error: error) + Logger.log(message: "Unable to cache Yat Visualisation: \(error.localizedDescription)", domain: .general, level: .error) return nil } } diff --git a/MobileWallet/Screens/Send/YatTransaction/YatTransactionModel.swift b/MobileWallet/Screens/Send/YatTransaction/YatTransactionModel.swift index bfe4e77b..5fd55126 100644 --- a/MobileWallet/Screens/Send/YatTransaction/YatTransactionModel.swift +++ b/MobileWallet/Screens/Send/YatTransaction/YatTransactionModel.swift @@ -125,7 +125,7 @@ final class YatTransactionModel { } play(fileData: cachedFileData) - TariLogger.info("Play Yat visualisation from local cache") + Logger.log(message: "Play Yat visualisation from local cache", domain: .general, level: .info) } private func download(assetURL: URL) { @@ -143,7 +143,7 @@ final class YatTransactionModel { private func save(videoData: Data, name: String) { guard let fileData = cacheManager.save(data: videoData, name: name) else { return } play(fileData: fileData) - TariLogger.info("Play Yat visualisation from web") + Logger.log(message:"Play Yat visualisation from web", domain: .general, level: .info) } private func play(fileData: YatCacheManager.FileData) { diff --git a/MobileWallet/Screens/Settings/AdvancedSettings/DeleteWalletViewController.swift b/MobileWallet/Screens/Settings/AdvancedSettings/DeleteWalletViewController.swift index ed1977cd..c47d1f56 100644 --- a/MobileWallet/Screens/Settings/AdvancedSettings/DeleteWalletViewController.swift +++ b/MobileWallet/Screens/Settings/AdvancedSettings/DeleteWalletViewController.swift @@ -165,8 +165,9 @@ class DeleteWalletViewController: UIViewController { } private func deleteWallet() { - TariLib.shared.deleteWallet() - AppRouter.moveToSplashScreen() + Tari.shared.deleteWallet() + Tari.shared.canAutomaticalyReconnectWallet = false + AppRouter.transitionToSplashScreen() } } diff --git a/MobileWallet/Screens/Settings/BackUpSettings/BackupWalletSettingsViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/BackupWalletSettingsViewController.swift index 41751ee5..23d39a08 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/BackupWalletSettingsViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/BackupWalletSettingsViewController.swift @@ -166,37 +166,13 @@ class BackupWalletSettingsViewController: SettingsParentTableViewController { } private func createWalletBackup() { - if TariLib.shared.walletState != .started { - TariEventBus.onMainThread(self, eventType: .walletStateChanged) { - [weak self] - (sender) in - guard let self = self else { return } - let walletState = sender!.object as! TariLib.WalletState - switch walletState { - case .started: - TariEventBus.unregister(self, eventType: .walletStateChanged) - do { - let password = AppKeychainWrapper.loadBackupPasswordFromKeychain() - try ICloudBackup.shared.createWalletBackup(password: password) - } catch { - self.failedToCreateBackup(error: error) - } - self.reloadTableViewWithAnimation() - case .startFailed: - TariEventBus.unregister(self, eventType: .walletStateChanged) - default: - break - } - } - } else { - do { - let password = AppKeychainWrapper.loadBackupPasswordFromKeychain() - try ICloudBackup.shared.createWalletBackup(password: password) - } catch { - failedToCreateBackup(error: error) - } - reloadTableViewWithAnimation() + do { + let password = AppKeychainWrapper.loadBackupPasswordFromKeychain() + try ICloudBackup.shared.createWalletBackup(password: password) + } catch { + failedToCreateBackup(error: error) } + reloadTableViewWithAnimation() } override func failedToCreateBackup(error: Error) { diff --git a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListModel.swift b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListModel.swift index 8f9f7fed..27adfeb7 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListModel.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/Seed Words List/SeedWordsListModel.swift @@ -50,7 +50,7 @@ final class SeedWordsListModel { // MARK: - Actions func fetchSeedWords() { - seedWords = TariLib.shared.tariWallet?.seedWords.0 ?? [] + seedWords = (try? Tari.shared.recovery.seedWords) ?? [] } func update(checkBoxStatus: Bool) { diff --git a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsModel.swift b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsModel.swift index 127f77fd..4a47efbc 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsModel.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsModel.swift @@ -65,11 +65,6 @@ final class VerifySeedWordsModel { init(inputData: InputData) { self.inputData = inputData - - if #available(iOS 15, *) { - fetchData() - } - setupCallbacks() } @@ -82,16 +77,11 @@ final class VerifySeedWordsModel { // MARK: - Setups private func setupCallbacks() { - if #available(iOS 14.0, *) { - $selectedTokenModels - .map(\.isEmpty) - .assign(to: &$isSelectedTokenTipVisible) - } else { - $selectedTokenModels - .map(\.isEmpty) - .assign(to: \.isSelectedTokenTipVisible, on: self) - .store(in: &cancellables) - } + + $selectedTokenModels + .map(\.isEmpty) + .assign(to: \.isSelectedTokenTipVisible, on: self) + .store(in: &cancellables) $selectedTokenModels .sink { [weak self] in self?.handle(selectedTokens: $0) } diff --git a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift index 9e7a21c1..fac05c1d 100644 --- a/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift +++ b/MobileWallet/Screens/Settings/BackUpSettings/VerifySeedWords/VerifySeedWordsViewController.swift @@ -70,13 +70,7 @@ final class VerifySeedWordsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupCallbacks() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - if #available(iOS 15, *) {} else { - model.fetchData() - } + model.fetchData() } // MARK: - Setups diff --git a/MobileWallet/Screens/Settings/CustomBridgesHandable.swift b/MobileWallet/Screens/Settings/CustomBridgesHandable.swift new file mode 100644 index 00000000..961adfaa --- /dev/null +++ b/MobileWallet/Screens/Settings/CustomBridgesHandable.swift @@ -0,0 +1,73 @@ +// CustomBridgesHandable.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 02/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import Combine + +protocol CustomBridgesHandable: UIViewController { + var navigationBar: NavigationBar { get } + var tableView: UITableView { get } +} + +extension CustomBridgesHandable { + + func setupCustomBridgeProgressHandler() -> AnyCancellable { + Tari.shared.connectionMonitor.$torBootstrapProgress + .map { Float($0) / 100.0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.navigationBar.setProgress($0) } + } + + func onCustomBridgeSuccessAction() { + navigationBar.setProgress(1.0) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.navigationBar.progressView.isHidden = true + self.view.isUserInteractionEnabled = true + self.tableView.reloadData() + } + } + + func onCustomBridgeFailureAction(error: Error) { + navigationBar.progressView.isHidden = true + view.isUserInteractionEnabled = true + let errorMessage = ErrorMessageManager.errorModel(forError: error) + PopUpPresenter.show(message: errorMessage) + } +} diff --git a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift index 8479b564..87340686 100644 --- a/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsParentTableViewController.swift @@ -61,25 +61,7 @@ class SettingsParentTableViewController: SettingsParentViewController { } @objc private func willEnterForeground() { - if TariLib.shared.walletState != .started { - TariEventBus.onMainThread(self, eventType: .walletStateChanged) { - [weak self] - (sender) in - guard let self = self else { return } - let walletState = sender!.object as! TariLib.WalletState - switch walletState { - case .started: - TariEventBus.unregister(self, eventType: .walletStateChanged) - self.reloadTableViewWithAnimation() - case .startFailed: - TariEventBus.unregister(self, eventType: .walletStateChanged) - default: - break - } - } - } else { - reloadTableViewWithAnimation() - } + reloadTableViewWithAnimation() } func updateMarks() { @@ -154,29 +136,3 @@ extension SettingsParentTableViewController { tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true } } - -extension SettingsParentTableViewController: OnionConnectorDelegate { - func onTorConnProgress(_ progress: Int) { - navigationBar.setProgress(Float(progress) / 100.0) - } - - func onTorConnDifficulties(error: OnionError) { - PopUpPresenter.show(message: MessageModel(title: error.failureReason ?? localized("Onion_Error.error.title.onionError"), message: error.localizedDescription, type: .error)) - } - - @objc func onTorConnFinished(_ configuration: BridgesConfuguration) { - OnionConnector.shared.removeObserver(self) - navigationBar.setProgress(1.0) - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.navigationBar.progressView.isHidden = true - self.view.isUserInteractionEnabled = true - self.tableView.reloadData() - } - } - - @objc func onTorConnDifficulties() { - OnionConnector.shared.removeObserver(self) - navigationBar.progressView.isHidden = true - view.isUserInteractionEnabled = true - } -} diff --git a/MobileWallet/Screens/Settings/SettingsViewController.swift b/MobileWallet/Screens/Settings/SettingsViewController.swift index 226f1309..359b6e4d 100644 --- a/MobileWallet/Screens/Settings/SettingsViewController.swift +++ b/MobileWallet/Screens/Settings/SettingsViewController.swift @@ -211,7 +211,11 @@ class SettingsViewController: SettingsParentTableViewController { private func onConnectYatAction() { - guard let publicKey = TariLib.shared.tariWallet?.publicKey.0?.hex.0 else { + let publicKey: String + + do { + publicKey = try Tari.shared.walletPublicKey.byteVector.hex + } catch { showNoConnectionError() return } @@ -393,7 +397,7 @@ extension SettingsViewController { private func update(baseNode: BaseNode) { do { - try TariLib.shared.update(baseNode: baseNode, syncAfterSetting: true) + try Tari.shared.connection.select(baseNode: baseNode) UIPasteboard.general.string = "" } catch { PopUpPresenter.show(message: MessageModel(title: localized("settings.pasteboard.custom_base_node.error.title"), message: localized("settings.pasteboard.custom_base_node.error.message"), type: .error)) diff --git a/MobileWallet/Common/Extensions/Wallet+Conveniences.swift b/MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift similarity index 77% rename from MobileWallet/Common/Extensions/Wallet+Conveniences.swift rename to MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift index 6764ab6a..e4547db2 100644 --- a/MobileWallet/Common/Extensions/Wallet+Conveniences.swift +++ b/MobileWallet/TariLib/Core/App Setup/AppConfigurator.swift @@ -1,10 +1,10 @@ -// Wallet+Conveniences.swift - +// AppConfigurator.swift + /* Package MobileWallet - Created by Adrian Truszczynski on 03/01/2022 + Created by Adrian Truszczynski on 11/10/2022 Using Swift 5.0 - Running on macOS 12.1 + Running on macOS 12.6 Copyright 2019 The Tari Project @@ -38,19 +38,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -extension Wallet { - - var totalBalance: MicroTari { - get throws { - let balance = try balance() - return MicroTari(balance.available + balance.incoming) - } - } - - var availableBalance: MicroTari { - get throws { - let balance = try balance() - return MicroTari(balance.available) +enum AppConfigurator { + + static func configureLoggers() { + switch TariSettings.shared.environment { + case .debug: + Logger.attach(logger: ConsoleLogger()) + case .testflight, .production: + Logger.attach(logger: CrashLogger()) } + + Logger.attach(logger: FileLogger()) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Signature.swift b/MobileWallet/TariLib/Core/Data Models/MessageMetadata.swift similarity index 80% rename from MobileWallet/TariLib/Wrappers/Utils/Signature.swift rename to MobileWallet/TariLib/Core/Data Models/MessageMetadata.swift index e7769302..4760670e 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Signature.swift +++ b/MobileWallet/TariLib/Core/Data Models/MessageMetadata.swift @@ -1,10 +1,10 @@ -// Signature.swift - +// MessageMetadata.swift + /* Package MobileWallet - Created by Jason van den Berg on 2020/02/07 + Created by Adrian Truszczynski on 18/09/2022 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 12.4 Copyright 2019 The Tari Project @@ -38,16 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - -struct Signature { +struct MessageMetadata { let hex: String let nonce: String - let message: String - let publicKey: PublicKey - - func isValid(wallet: Wallet) throws -> Bool { - // TODO don't join with pipe - return try wallet.verifyMessageSignature(publicKey: publicKey, signature: hex, nonce: nonce, message: message) - } } diff --git a/MobileWallet/TariLib/Core/Data Models/WalletBalance.swift b/MobileWallet/TariLib/Core/Data Models/WalletBalance.swift new file mode 100644 index 00000000..6a5910ab --- /dev/null +++ b/MobileWallet/TariLib/Core/Data Models/WalletBalance.swift @@ -0,0 +1,54 @@ +// WalletBalance.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 03/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +struct WalletBalance: Equatable { + + let available: UInt64 + let incoming: UInt64 + let outgoing: UInt64 + let timeLocked: UInt64 + + static var zero: Self { Self(available: 0, incoming: 0, outgoing: 0, timeLocked: 0) } +} + +extension WalletBalance { + var total: UInt64 { available + incoming } +} + diff --git a/MobileWallet/TariLib/Wrappers/Balance.swift b/MobileWallet/TariLib/Core/FFI/Balance.swift similarity index 77% rename from MobileWallet/TariLib/Wrappers/Balance.swift rename to MobileWallet/TariLib/Core/FFI/Balance.swift index 1b2933bc..e92fda2b 100644 --- a/MobileWallet/TariLib/Wrappers/Balance.swift +++ b/MobileWallet/TariLib/Core/FFI/Balance.swift @@ -38,47 +38,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - final class Balance { + + // MARK: - Properties + let pointer: OpaquePointer - - init (pointer: OpaquePointer) { - self.pointer = pointer - } - + var available: UInt64 { var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - balance_get_available(pointer, error)}) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = balance_get_available(pointer, errorCodePointer) guard errorCode == 0 else { return 0 } return result } var incoming: UInt64 { var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - balance_get_pending_incoming(pointer, error)}) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = balance_get_pending_incoming(pointer, errorCodePointer) guard errorCode == 0 else { return 0 } return result } var outgoing: UInt64 { var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - balance_get_pending_outgoing(pointer, error)}) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = balance_get_pending_outgoing(pointer, errorCodePointer) guard errorCode == 0 else { return 0 } return result } var timelocked: UInt64 { var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - balance_get_time_locked(pointer, error)}) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = balance_get_time_locked(pointer, errorCodePointer) guard errorCode == 0 else { return 0 } return result } + // MARK: - Initialisers + + init (pointer: OpaquePointer) { + self.pointer = pointer + } + + // MARK: - Deinitialiser + deinit { balance_destroy(pointer) } diff --git a/MobileWallet/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift b/MobileWallet/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift new file mode 100644 index 00000000..3820ecf2 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/BaseNodeConnectivityStatus.swift @@ -0,0 +1,45 @@ +// BaseNodeConnectivityStatus.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 03/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum BaseNodeConnectivityStatus: UInt64 { + case connecting + case online + case offline +} diff --git a/MobileWallet/TariLib/Core/FFI/ByteVector.swift b/MobileWallet/TariLib/Core/FFI/ByteVector.swift new file mode 100644 index 00000000..037e37ca --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/ByteVector.swift @@ -0,0 +1,117 @@ +// ByteVector.swift + +/* + Package MobileWallet + Created by Jason van den Berg on 2019/11/14 + Using Swift 5.0 + Running on macOS 10.15 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class ByteVector { + + // MARK: - Properties + + let pointer: OpaquePointer + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = byte_vector_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + // MARK: - Constructors + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + convenience init(data: Data) throws { + + let byteArray = [UInt8](data) + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = byte_vector_create(byteArray, UInt32(byteArray.count), errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + + self.init(pointer: pointer) + } + + // MARK: - Actions + + func byte(index: UInt32) throws -> UInt8 { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = byte_vector_get_at(pointer, index, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + deinit { + byte_vector_destroy(pointer) + } +} + +extension ByteVector { + + var hex: String { + get throws { + try bytes + .map { String(format: "%02hhx", $0) } + .joined() + } + } + + var bytes: [UInt8] { + get throws { + let count = try count + return try (0..(mutating: (alias as NSString).utf8String) + + convenience init(alias: String, publicKeyPointer: OpaquePointer) throws { + var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - contact_create(aliasPointer, publicKey.pointer, error) - }) - guard errorCode == 0 else { - throw ContactError.generic(errorCode) - } - ptr = result! + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = contact_create(alias, publicKeyPointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + + self.init(pointer: pointer) } - + + // MARK: - Deinitialiser + deinit { - contact_destroy(ptr) + contact_destroy(pointer) } } diff --git a/MobileWallet/TariLib/Core/FFI/Contacts/Contacts.swift b/MobileWallet/TariLib/Core/FFI/Contacts/Contacts.swift new file mode 100644 index 00000000..d8fa1e0a --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Contacts/Contacts.swift @@ -0,0 +1,98 @@ +// Contacts.swift + +/* + Package MobileWallet + Created by Jason van den Berg on 2019/11/16 + Using Swift 5.0 + Running on macOS 10.15 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class Contacts { + + // MARK: - Properties + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = contacts_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialisers + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + convenience init(walletPointer: OpaquePointer) throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_get_contacts(walletPointer, errorCodePointer) + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + + self.init(pointer: pointer) + } + + // MARK: - Actions + + func contact(index: UInt32) throws -> Contact { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = contacts_get_at(pointer, index, errorCodePointer) + guard let result = result else { throw WalletError(code: errorCode) } + return Contact(pointer: result) + } + + // MARK: - Deinitialiser + + deinit { + contacts_destroy(pointer) + } +} + +extension Contacts { + + var all: [Contact] { + get throws { + let count = try count + return try (0.. ByteVector { + + // MARK: - Actions + + func emmojiSet(index: UInt32) throws -> ByteVector { + var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - emoji_set_get_at(ptr, position, error) - }) - - guard errorCode == 0 else { - throw EmojiSetError.generic(errorCode) - } - - return ByteVector(pointer: result!) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = emoji_set_get_at(pointer, index, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + return ByteVector(pointer: result) } - - var list: ([String], Error?) { - var result: [String] = [] - let (count, countError) = self.count - guard countError == nil else { - return ([], countError) - } - - if count > 0 { - for n in 0...count - 1 { - do { - let bytes = try self.at(position: n) - let (emoji, emojiError) = bytes.utf8 - guard emojiError == nil else { - return ([], emojiError) - } - - result.append(emoji!) - } catch { - return ([], error) - } - } - } - - return (result, nil) + + // MARK: - Deinitialiser + + deinit { + emoji_set_destroy(pointer) } +} - deinit { - emoji_set_destroy(ptr) +extension EmojiSet { + + var all: [ByteVector] { + get throws { + let count = try count + return try (0.. Bool { + guard let leftHex = try? lhs.byteVector.hex, let rightHex = try? rhs.byteVector.hex else { return false } + return leftHex == rightHex + } +} + +private extension CharacterSet { + static var hexadecimal: Self { CharacterSet(charactersIn: "0123456789abcdef") } +} + diff --git a/MobileWallet/TariLib/Core/FFI/RestoreWalletStatus.swift b/MobileWallet/TariLib/Core/FFI/RestoreWalletStatus.swift new file mode 100644 index 00000000..8d28de11 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/RestoreWalletStatus.swift @@ -0,0 +1,72 @@ +// RestoreWalletStatus.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 03/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum RestoreWalletStatus { + case unknown + case connectingToBaseNode + case connectedToBaseNode + case connectionFailed(attempt: UInt64, maxAttempts: UInt64) + case progress(restoredUTXOs: UInt64, totalNumberOfUTXOs: UInt64) + case completed + case scanningRoundFailed(attempt: UInt64, maxAttempts: UInt64) + case recoveryFailed + + init(status: UInt8, firstValue: UInt64, secondValue: UInt64) { + + switch status { + case 0: + self = .connectingToBaseNode + case 1: + self = .connectedToBaseNode + case 2: + self = .connectionFailed(attempt: firstValue, maxAttempts: secondValue) + case 3: + self = .progress(restoredUTXOs: firstValue, totalNumberOfUTXOs: secondValue) + case 4: + self = .completed + case 5: + self = .scanningRoundFailed(attempt: firstValue, maxAttempts: secondValue) + case 6: + self = .recoveryFailed + default: + self = .unknown + } + } +} diff --git a/MobileWallet/TariLib/Core/FFI/Seed Words/SeedWords.swift b/MobileWallet/TariLib/Core/FFI/Seed Words/SeedWords.swift new file mode 100644 index 00000000..2537ec83 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Seed Words/SeedWords.swift @@ -0,0 +1,154 @@ +// SeedWords.swift + +/* + Package MobileWallet + Created by David Main on 6/11/20 + Using Swift 5.0 + Running on macOS 10.15 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class SeedWords { + + enum InternalError: Int, CoreError { + case invalidSeedWord + case invalidSeedPhrase + case unexpectedResult + case phraseIsTooShort + case phraseIsTooLong + + var code: Int { rawValue } + var domain: String { "SWE" } + } + + enum PushWordResult: UInt8 { + case invalidSeedWord + case successfulPush + case seedPhraseComplete + case invalidSeedPhrase + } + + // MARK: - Properties + + let pointer: OpaquePointer + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = seed_words_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + // MARK: - Initializers + + init(walletPointer: OpaquePointer) throws { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_seed_words(walletPointer, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + pointer = result + } + + init() { + pointer = seed_words_create() + } + + // MARK: - Actions + + func push(word: String) throws -> PushWordResult { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = seed_words_push_word(pointer, word, errorCodePointer) + + guard errorCode == 0, let pushResult = PushWordResult(rawValue: result) else { throw WalletError(code: errorCode) } + return pushResult + } + + // MARK: - Actions + + func word(index: UInt32) throws -> String { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = seed_words_get_at(pointer, index, errorCodePointer) + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) + } + + // MARK: - Deinitialization + + deinit { + seed_words_destroy(pointer) + } +} + +extension SeedWords { + + var all: [String] { + get throws { + let count = try count + return try (0.. String { var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - seed_words_get_at(pointer, index, $0) - } - - guard errorCode == 0 else { throw Error.generic(code: errorCode) } - guard let result = result else { throw Error.internalError } + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = seed_words_get_at(pointer, index, errorCodePointer) + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } return String(cString: result) } - // MARK: - Deinitalisation + // MARK: - Deinitalisator deinit { seed_words_destroy(pointer) } } +extension SeedWordsMnemonicWordList { + + enum Language: String { + case english = "English" + case spanish = "Spanish" + } + + var seedWords: [String] { + get throws { + let length = try listLength + return try (0.. UInt32 { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let result = fee_per_gram_stats_get_length(pointer, errorCodePointer) - - guard errorCode == 0 else { throw WalletError(code: errorCode) } - return result + init(pointer: OpaquePointer) { + self.pointer = pointer } + // MARK: - Actions + func element(at index: UInt32) throws -> OpaquePointer { var errorCode: Int32 = -1 @@ -122,6 +131,8 @@ final class TariFeePerGramStats { return result } + // MARK: - Deinitialisers + deinit { fee_per_gram_stats_destroy(pointer) } diff --git a/MobileWallet/TariLib/Wrappers/TariVectorWrapper.swift b/MobileWallet/TariLib/Core/FFI/TariVectorWrapper.swift similarity index 94% rename from MobileWallet/TariLib/Wrappers/TariVectorWrapper.swift rename to MobileWallet/TariLib/Core/FFI/TariVectorWrapper.swift index 9e20efda..421ae382 100644 --- a/MobileWallet/TariLib/Wrappers/TariVectorWrapper.swift +++ b/MobileWallet/TariLib/Core/FFI/TariVectorWrapper.swift @@ -40,12 +40,18 @@ final class TariVectorWrapper { + // MARK: - Properties + let pointer: UnsafeMutablePointer + // MARK: - Initialisers + init(type: TariTypeTag) { pointer = create_tari_vector(type) } + // MARK: - Actions + func add(commitment: String) throws { var errorCode: Int32 = -1 @@ -56,15 +62,20 @@ final class TariVectorWrapper { guard errorCode == 0 else { throw WalletError(code: errorCode) } } - func add(commitments: [String]) throws { - try commitments.forEach { try self.add(commitment: $0) } - } + // MARK: - Deinitialiser deinit { destroy_tari_vector(pointer) } } +extension TariVectorWrapper { + + func add(commitments: [String]) throws { + try commitments.forEach { try self.add(commitment: $0) } + } +} + extension UnsafeMutablePointer where Pointee == TariVector { func array() -> [T] { diff --git a/MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxsProtocol.swift b/MobileWallet/TariLib/Core/FFI/TransactionValidationData.swift similarity index 82% rename from MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxsProtocol.swift rename to MobileWallet/TariLib/Core/FFI/TransactionValidationData.swift index d39c9273..43c07b70 100644 --- a/MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxsProtocol.swift +++ b/MobileWallet/TariLib/Core/FFI/TransactionValidationData.swift @@ -1,10 +1,10 @@ -// TxsProtocol.swift - +// TransactionValidationData.swift + /* Package MobileWallet - Created by Jason van den Berg on 2020/01/17 + Created by Adrian Truszczynski on 03/10/2022 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 12.4 Copyright 2019 The Tari Project @@ -38,13 +38,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - -protocol TxsProtocol { - associatedtype Tx: TxProtocol +struct TransactionValidationData { + let identifier: UInt64 + let isSuccess: Bool +} - var pointer: OpaquePointer { get } - var count: (UInt32, Error?) { get } - var list: ([Tx], Error?) { get } - func at(position: UInt32) throws -> Tx +struct TransactionOutputValidationData { + let identifier: UInt64 + let status: TxoValidationStatus } diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift new file mode 100644 index 00000000..3c9d0648 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransaction.swift @@ -0,0 +1,216 @@ +// CompletedTransaction.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 26/09/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class CompletedTransaction: Transaction { + + enum RejectionReason: Int32 { + case notCancelled = -1 + case unknown + case userCancelled + case timeout + case doubleSpend + case orphan + case timeLocked + case invalidTransaction + case abandonedCoinbase + + init(code: Int32) { + self = Self(rawValue: code) ?? .unknown + } + } + + // MARK: - Protocol + + var publicKey: PublicKey { + get throws { try isOutboundTransaction ? destinationPublicKey : sourcePublicKey } + } + + let isCancelled: Bool + var isPending: Bool { false } + + // MARK: - Properties + + var identifier: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_transaction_id(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var confirmationCount: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_confirmations(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var amount: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_amount(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var fee: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_fee(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var message: String { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_message(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) + } + } + + var timestamp: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_timestamp(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var sourcePublicKey: PublicKey { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_source_public_key(pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: pointer) + } + } + + var destinationPublicKey: PublicKey { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_destination_public_key(pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: pointer) + } + } + + var transactionKernel: CompletedTransactionKernel { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_transaction_kernel(pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return CompletedTransactionKernel(pointer: pointer) + } + } + + var status: TransactionStatus { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_status(pointer, errorCodePointer) + + guard errorCode == 0, let status = TransactionStatus(rawValue: result) else { throw WalletError(code: errorCode) } + return status + } + } + + var isOutboundTransaction: Bool { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_is_outbound(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var rejectionReason: RejectionReason { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transaction_get_cancellation_reason(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return RejectionReason(code: result) + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialiser + + init(pointer: OpaquePointer, isCancelled: Bool) { + self.pointer = pointer + self.isCancelled = isCancelled + } + + // MARK: - Deinitialiser + + deinit { + completed_transaction_destroy(pointer) + } +} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/CompletedTxKernel.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift similarity index 56% rename from MobileWallet/TariLib/Wrappers/Transactions/CompletedTxKernel.swift rename to MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift index 442652ba..2e2151db 100644 --- a/MobileWallet/TariLib/Wrappers/Transactions/CompletedTxKernel.swift +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactionKernel.swift @@ -1,10 +1,10 @@ -// CompletedTxKernel.swift - +// CompletedTransactionKernel.swift + /* Package MobileWallet - Created by David Main on 9/2/21 + Created by Adrian Truszczynski on 26/09/2022 Using Swift 5.0 - Running on macOS 11.5 + Running on macOS 12.4 Copyright 2019 The Tari Project @@ -38,61 +38,53 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - -enum CompletedTxKernelError: Error { - case generic(_ errorCode: Int32) -} - -final class CompletedTxKernel { - let pointer : OpaquePointer - - var excess: String { - get throws { +final class CompletedTransactionKernel { + + // MARK: - Properties + + var excessHex: String { + get throws { var errorCode: Int32 = -1 - let excessPointer = withUnsafeMutablePointer(to: &errorCode, { error in - transaction_kernel_get_excess_hex(pointer, error) - }) - guard errorCode == 0 else { - throw ContactError.generic(errorCode) - } - - return String(validatingUTF8: excessPointer!)! + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = transaction_kernel_get_excess_hex(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) } } - - var excessPublicNonce: String { + + var excessPublicNonceHex: String { get throws { var errorCode: Int32 = -1 - let noncePointer = withUnsafeMutablePointer(to: &errorCode, { error in - transaction_kernel_get_excess_public_nonce_hex(pointer, error) - }) - guard errorCode == 0 else { - throw ContactError.generic(errorCode) - } - - return String(validatingUTF8: noncePointer!)! + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = transaction_kernel_get_excess_public_nonce_hex(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) } } - - var excessSignature: String { + + var excessSignatureHex: String { get throws { var errorCode: Int32 = -1 - let signaturePointer = withUnsafeMutablePointer(to: &errorCode, { error in - transaction_kernel_get_excess_signature_hex(pointer, error) - }) - guard errorCode == 0 else { - throw ContactError.generic(errorCode) - } - - return String(validatingUTF8: signaturePointer!)! + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = transaction_kernel_get_excess_signature_hex(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) } } - + + private let pointer: OpaquePointer + + // MARK: - Initialiser + init(pointer: OpaquePointer) { self.pointer = pointer } - + + // MARK: - Deinitialiser + deinit { transaction_kernel_destroy(pointer) } diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift new file mode 100644 index 00000000..30b53f9e --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Completed/CompletedTransactions.swift @@ -0,0 +1,80 @@ +// CompletedTransactions.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 24/08/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class CompletedTransactions { + + // MARK: - Properties + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transactions_get_length(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialiser + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + // MARK: - Actions + + func transaction(at index: UInt32, isCancelled: Bool) throws -> CompletedTransaction { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = completed_transactions_get_at(pointer, index, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return CompletedTransaction(pointer: pointer, isCancelled: isCancelled) + } + + // MARK: - Deinitialiser + + deinit { + completed_transactions_destroy(pointer) + } +} diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift new file mode 100644 index 00000000..a4205b40 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransaction.swift @@ -0,0 +1,130 @@ +// PendingInboundTransaction.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 26/09/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class PendingInboundTransaction: Transaction { + + // MARK: - Protocol + + var isOutboundTransaction: Bool { false } + var isCancelled: Bool { false } + var isPending: Bool { true } + + // MARK: - Properties + + var identifier: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_transaction_id(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var amount: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_amount(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var message: String { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_message(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) + } + } + + var timestamp: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_timestamp(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var publicKey: PublicKey { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_source_public_key(pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: pointer) + } + } + + var status: TransactionStatus { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transaction_get_status(pointer, errorCodePointer) + + guard errorCode == 0, let status = TransactionStatus(rawValue: result) else { throw WalletError(code: errorCode) } + return status + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialiser + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + // MARK: - Deinitialiser + + deinit { + pending_inbound_transaction_destroy(pointer) + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/BaseNodeStatusMonitor.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift similarity index 59% rename from MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/BaseNodeStatusMonitor.swift rename to MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift index c6a870a8..ee0858d0 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/BaseNodeStatusMonitor.swift +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Inbound/PendingInboundTransactions.swift @@ -1,8 +1,8 @@ -// BaseNodeStatusMonitor.swift +// PendingInboundTransactions.swift /* Package MobileWallet - Created by Adrian Truszczynski on 18/07/2022 + Created by Adrian Truszczynski on 25/08/2022 Using Swift 5.0 Running on macOS 12.4 @@ -38,47 +38,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Combine - -final class BaseNodeStatusMonitor { - - enum SyncStatus { - case idle - case success - case pending - case failure - } +final class PendingInboundTransactions { // MARK: - Properties - @Published private(set) var syncStatus: SyncStatus = .idle - @Published private(set) var connectionStatus: BaseNodeConnectivityStatus = .offline + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transactions_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } - private var cancellables = Set() + private let pointer: OpaquePointer // MARK: - Initialisers - init() { - setupCallbacks() + init(pointer: OpaquePointer) { + self.pointer = pointer } - // MARK: - Setups + // MARK: - Actions - private func setupCallbacks() { - - TariEventBus.events(forType: .baseNodeSyncStarted) - .sink { [weak self] _ in self?.syncStatus = .pending } - .store(in: &cancellables) + func transaction(at index: UInt32) throws -> PendingInboundTransaction { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_inbound_transactions_get_at(pointer, index, errorCodePointer) - TariEventBus.events(forType: .baseNodeSyncComplete) - .compactMap { $0.object as? [String: Any] } - .compactMap { $0["success"] as? Bool } - .map { $0 ? .success : .failure } - .assign(to: \.syncStatus, on: self) - .store(in: &cancellables) - - TariLib.shared.$baseNodeConnectionStatus - .assign(to: \.connectionStatus, on: self) - .store(in: &cancellables) + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PendingInboundTransaction(pointer: pointer) + } + + // MARK: - Deinitialiser + + deinit { + pending_inbound_transactions_destroy(pointer) } } diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift new file mode 100644 index 00000000..7e279f82 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransaction.swift @@ -0,0 +1,143 @@ +// PendingOutboundTransaction.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 26/09/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class PendingOutboundTransaction: Transaction { + + // MARK: - Protocol + + var isOutboundTransaction: Bool { true } + var isCancelled: Bool { false } + var isPending: Bool { true } + + // MARK: - Properties + + var identifier: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_transaction_id(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var amount: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_amount(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var fee: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_fee(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var message: String { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_message(pointer, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) + } + } + + var timestamp: UInt64 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_timestamp(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + var publicKey: PublicKey { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_destination_public_key(pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PublicKey(pointer: pointer) + } + } + + var status: TransactionStatus { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transaction_get_status(pointer, errorCodePointer) + + guard errorCode == 0, let status = TransactionStatus(rawValue: result) else { throw WalletError(code: errorCode) } + return status + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialisers + + init(pointer: OpaquePointer) { + self.pointer = pointer + + } + + // MARK: - Deinitialiser + + deinit { + pending_outbound_transaction_destroy(pointer) + } +} + diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift new file mode 100644 index 00000000..f1266c2f --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Pending Outbound/PendingOutboundTransactions.swift @@ -0,0 +1,79 @@ +// PendingOutboundTransactions.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 25/08/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class PendingOutboundTransactions { + + // MARK: - Properties + + var count: UInt32 { + get throws { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transactions_get_length(pointer, errorCodePointer) + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + } + + private let pointer: OpaquePointer + + // MARK: - Initialisers + + init(pointer: OpaquePointer) { + self.pointer = pointer + } + + // MARK: - Actions + + func transaction(at index: UInt32) throws -> PendingOutboundTransaction { + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = pending_outbound_transactions_get_at(pointer, index, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PendingOutboundTransaction(pointer: pointer) + } + + // MARK: - Deinitialiser + + deinit { + pending_outbound_transactions_destroy(pointer) + } +} diff --git a/MobileWallet/Common/Tracker.swift b/MobileWallet/TariLib/Core/FFI/Transactions/Transaction.swift similarity index 58% rename from MobileWallet/Common/Tracker.swift rename to MobileWallet/TariLib/Core/FFI/Transactions/Transaction.swift index 87611b6f..5634f0a3 100644 --- a/MobileWallet/Common/Tracker.swift +++ b/MobileWallet/TariLib/Core/FFI/Transactions/Transaction.swift @@ -1,10 +1,10 @@ -// Tracker.swift - +// Transaction.swift + /* Package MobileWallet - Created by Jason van den Berg on 2020/03/26 + Created by Adrian Truszczynski on 26/09/2022 Using Swift 5.0 - Running on macOS 10.15 + Running on macOS 12.4 Copyright 2019 The Tari Project @@ -38,34 +38,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation -import MatomoTracker - -class Tracker { - private static let matomoUrl = "https://matomo.tari.com/matomo.php" - private static let matomoSiteId = "2" - - private let matomoTracker: MatomoTracker - - static let shared = Tracker() +enum TransactionStatus: Int32 { + case txNullError = -1 + case completed + case broadcast + case minedUnconfirmed + case imported + case pending + case minedConfirmed + case rejected + case fauxUnconfirmed + case fauxConfirmed + case unknown +} - private init() { - matomoTracker = MatomoTracker(siteId: Tracker.matomoSiteId, baseURL: URL(string: Tracker.matomoUrl)!) +protocol Transaction { + var identifier: UInt64 { get throws } + var amount: UInt64 { get throws } + var isOutboundTransaction: Bool { get throws } + var status: TransactionStatus { get throws } + var message: String { get throws } + var timestamp: UInt64 { get throws } + var publicKey: PublicKey { get throws } + var isCancelled: Bool { get } + var isPending: Bool { get } +} - if TariSettings.shared.environment != .production { - TariLogger.warn("Opting out of tracking") - matomoTracker.isOptedOut = true +extension Transaction { + + var isOneSidedPayment: Bool { + get throws { + let status = try status + return status == .fauxConfirmed || status == .fauxUnconfirmed } } - - func track(_ page: String, _ title: String) { - TariLogger.verbose("Tracking: \(page)") - let view: [String] = page.components(separatedBy: "?") - matomoTracker.track(view: view) - } - - func track(eventWithCategory category: String, action: String, name: String? = nil) { - TariLogger.verbose("Tracking: \(action)") - matomoTracker.track(eventWithCategory: category, action: action, name: name, url: nil) + + var formattedTimestamp: String { + get throws { Date(timeIntervalSince1970: Double(try timestamp)).relativeDayFromToday() ?? "" } } } diff --git a/MobileWallet/TariLib/Core/FFI/Transactions/TransactionSendResult.swift b/MobileWallet/TariLib/Core/FFI/Transactions/TransactionSendResult.swift new file mode 100644 index 00000000..b67227ec --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Transactions/TransactionSendResult.swift @@ -0,0 +1,116 @@ +// TransactionSendResult.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 03/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TransactionSendResult { + + enum Status: UInt32 { + case queued + case directSendSafSend + case directSend + case safSend + case invalid + } + + enum InternalError: Error { + case invalidStatus + } + + // MARK: - Properties + + let identifier: UInt64 + let rawStatus: UInt32 + let status: Status + private let pointer: OpaquePointer + + // MARK: - Initialisers + + init(identifier: UInt64, pointer: OpaquePointer) throws { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = transaction_send_status_decode(pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + guard let status = Status(rawValue: result) else { throw InternalError.invalidStatus } + + self.identifier = identifier + self.rawStatus = result + self.status = status + self.pointer = pointer + } + + // MARK: - Deinitialiser + + deinit { + transaction_send_status_destroy(pointer) + } +} + +extension TransactionSendResult.Status { + + var isSuccess: Bool { isDirectSend || isSafSend || !isQueued } + + private var isDirectSend: Bool { + switch self { + case .directSend, .directSendSafSend: + return true + case .queued, .safSend, .invalid: + return false + } + } + + private var isSafSend: Bool { + switch self { + case .directSendSafSend, .safSend: + return true + case .queued, .directSend, .invalid: + return false + } + } + + private var isQueued: Bool { + switch self { + case .queued: + return true + case .directSendSafSend, .directSend, .safSend, .invalid: + return false + } + } +} diff --git a/MobileWallet/TariLib/Core/FFI/TxoValidationStatus.swift b/MobileWallet/TariLib/Core/FFI/TxoValidationStatus.swift new file mode 100644 index 00000000..f67c3cc0 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/TxoValidationStatus.swift @@ -0,0 +1,46 @@ +// TxoValidationStatus.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum TxoValidationStatus: UInt64 { + case success + case alreadyBusy + case internalFailure + case communicationFailure +} diff --git a/MobileWallet/TariLib/Core/FFI/Wallet.swift b/MobileWallet/TariLib/Core/FFI/Wallet.swift new file mode 100644 index 00000000..e6d32dcf --- /dev/null +++ b/MobileWallet/TariLib/Core/FFI/Wallet.swift @@ -0,0 +1,168 @@ +// WalletV2.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 09/11/2021 + Using Swift 5.0 + Running on macOS 12.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class Wallet { + + // MARK: - Constants + + private static let numberOfRollingLogFiles: UInt32 = 2 + private static let logFileSize: UInt32 = 10 * 1024 * 1024 + private static let isRecoveryInProgress: Bool = false + + // MARK: - Properties + + let pointer: OpaquePointer + + // MARK: - Initialisers + + init(commsConfig: CommsConfig, loggingFilePath: String, seedWords: SeedWords?, passphrase: String?, networkName: String) throws { + + let receivedTransactionCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .receivedTransaction, object: pointer) + } + + let receivedTransactionReplyCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .receivedTransactionReply, object: pointer) + } + + let receivedFinalizedTransactionCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .receivedFinalizedTransaction, object: pointer) + } + + let fauxTransactionConfirmedCallback: (@convention(c) (OpaquePointer?) -> Void)? = { pointer in + WalletCallbacksManager.shared.post(name: .fauxTransactionConfirmed, object: pointer) + } + + let fauxTransactionUncorfirmedCallback: (@convention(c) (OpaquePointer?, UInt64) -> Void)? = { pointer, reason in + WalletCallbacksManager.shared.post(name: .fauxTransactionUnconfirmed, object: pointer) + } + + let transactionSendResultCallback: (@convention(c) (UInt64, OpaquePointer?) -> Void) = { identifier, pointer in + guard let pointer = pointer, let data = try? TransactionSendResult(identifier: identifier, pointer: pointer) else { return } + WalletCallbacksManager.shared.post(name: .transactionSendResult, object: data) + } + + let transactionBroadcastCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .transactionBroadcast, object: pointer) + } + + let transactionMinedCallback: @convention(c) (OpaquePointer?) -> Void = { pointer in + WalletCallbacksManager.shared.post(name: .transactionMined, object: pointer) + } + + let unconfirmedTransactionMinedCallback: @convention(c) (OpaquePointer?, UInt64) -> Void = { pointer, confirmationCount in + WalletCallbacksManager.shared.post(name: .unconfirmedTransactionMined, object: pointer) + } + + let transactionCancellationCallback: @convention(c) (OpaquePointer?, UInt64) -> Void = { pointer, rejectonReason in + WalletCallbacksManager.shared.post(name: .transactionCancellation, object: pointer) + } + + let txoValidationCallback: @convention(c) (UInt64, UInt64) -> Void = { responseId, status in + guard let status = TxoValidationStatus(rawValue: status) else { return } + WalletCallbacksManager.shared.post(name: .transactionOutputValidation, object: TransactionOutputValidationData(identifier: responseId, status: status)) + } + + let contactsLivenessDataUpdatedCallback: (@convention(c) (OpaquePointer?) -> Void) = { _ in + } + + let balanceUpdatedCallback: @convention(c) (OpaquePointer?) -> Void = { balancePointer in + guard let balancePointer = balancePointer else { return } + let balance = Balance(pointer: balancePointer) + WalletCallbacksManager.shared.post(name: .walletBalanceUpdate, object: balance) + + } + + let trasactionValidationCompleteCallback: @convention(c) (UInt64, Bool) -> Void = { responseId, isSuccess in + WalletCallbacksManager.shared.post(name: .transactionValidation, object: TransactionValidationData(identifier: responseId, isSuccess: isSuccess)) + } + + let storedMessagesReceivedCallback: (@convention(c) () -> Void) = { + } + + let connectivityStatusCallback: (@convention(c) (UInt64) -> Void) = { rawStatus in + guard let status = BaseNodeConnectivityStatus(rawValue: rawStatus) else { return } + WalletCallbacksManager.shared.post(name: .baseNodeConnectionStatusUpdate, object: status) + } + + var isRecoveryInProgress = false + var errorCode: Int32 = -1 + + let isRecoveryInProgressPointer = PointerHandler.pointer(for: &isRecoveryInProgress) + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_create( + commsConfig.pointer, + loggingFilePath, + Self.numberOfRollingLogFiles, + Self.logFileSize, + passphrase, + seedWords?.pointer, + networkName, + receivedTransactionCallback, + receivedTransactionReplyCallback, + receivedFinalizedTransactionCallback, + transactionBroadcastCallback, + transactionMinedCallback, + unconfirmedTransactionMinedCallback, + fauxTransactionConfirmedCallback, + fauxTransactionUncorfirmedCallback, + transactionSendResultCallback, + transactionCancellationCallback, + txoValidationCallback, + contactsLivenessDataUpdatedCallback, + balanceUpdatedCallback, + trasactionValidationCompleteCallback, + storedMessagesReceivedCallback, + connectivityStatusCallback, + isRecoveryInProgressPointer, + errorCodePointer + ) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + pointer = result + } + + // MARK: - Deinitialiser + + deinit { + wallet_destroy(pointer) + } +} diff --git a/MobileWallet/TariLib/Core/FFIWalletManager.swift b/MobileWallet/TariLib/Core/FFIWalletManager.swift new file mode 100644 index 00000000..bdb0d5c8 --- /dev/null +++ b/MobileWallet/TariLib/Core/FFIWalletManager.swift @@ -0,0 +1,515 @@ +// FFIWalletManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 07/01/2022 + Using Swift 5.0 + Running on macOS 12.1 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class FFIWalletManager { + + private enum BaseNodeValidationType: String { + case txo + case tx + } + + enum GeneralError: Error { + case unableToCreateWallet + } + + // MARK: - Properties + + @Published private(set) var baseNodeConnectionStatus: BaseNodeConnectivityStatus = .offline + + var isWalletConnected: Bool { wallet != nil } + + private var wallet: Wallet? + + private var exisingWallet: Wallet { + get throws { + guard let wallet = wallet else { throw GeneralError.unableToCreateWallet } + return wallet + } + } + + private var cancelables = Set() + + // MARK: - Initialisers + + init() { + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + WalletCallbacksManager.shared.baseNodeConnectionStatus + .assign(to: \.baseNodeConnectionStatus, on: self) + .store(in: &cancelables) + } + + // MARK: - Actions + + func connectWallet(commsConfig: CommsConfig, logFilePath: String, seedWords: SeedWords?, passphrase: String?, networkName: String) throws { + do { + wallet = try Wallet(commsConfig: commsConfig, loggingFilePath: logFilePath, seedWords: seedWords, passphrase: passphrase, networkName: networkName) + } catch { + wallet = nil + throw error + } + } + + func disconnectWallet() { + wallet = nil + baseNodeConnectionStatus = .offline + } + + // MARK: - FFI Actions + + func walletPublicKey() throws -> PublicKey { + let wallet = try exisingWallet + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + guard let result = wallet_get_public_key(wallet.pointer, errorCodePointer) else { throw WalletError(code: errorCode) } + return PublicKey(pointer: result) + } + + func walletContacts() throws -> Contacts { + let wallet = try exisingWallet + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_contacts(wallet.pointer, errorCodePointer) + guard let result = result else { throw WalletError(code: errorCode) } + return Contacts(pointer: result) + } + + func balance() throws -> Balance { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_balance(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + return Balance(pointer: result) + } + + func completedTransactions() throws -> CompletedTransactions { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_completed_transactions(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return CompletedTransactions(pointer: pointer) + } + + func cancelledTransactions() throws -> CompletedTransactions { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_cancelled_transactions(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return CompletedTransactions(pointer: pointer) + } + + func pendingInboundTransactions() throws -> PendingInboundTransactions { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_pending_inbound_transactions(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PendingInboundTransactions(pointer: pointer) + } + + func pendingOutboundTransactions() throws -> PendingOutboundTransactions { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_pending_outbound_transactions(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return PendingOutboundTransactions(pointer: pointer) + } + + func cancelPendingTransaction(identifier: UInt64) throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_cancel_pending_transaction(wallet.pointer, identifier, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func upsert(contact: Contact) throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_upsert_contact(wallet.pointer, contact.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func startTransactionOutputValidation() throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_start_txo_validation(wallet.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func startTransactionValidation() throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_start_transaction_validation(wallet.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func feeEstimate(amount: UInt64, feePerGram: UInt64, kernelsCount: UInt64, outputsCount: UInt64) throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_fee_estimate(wallet.pointer, amount, nil, feePerGram, kernelsCount, outputsCount, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func feePerGramStats(count: UInt32) throws -> TariFeePerGramStats { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_fee_per_gram_stats(wallet.pointer, count, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return TariFeePerGramStats(pointer: pointer) + } + + func addBaseNodePeer(publicKeyPointer: OpaquePointer, address: String) throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_add_base_node_peer(wallet.pointer, publicKeyPointer, address, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func utxos() throws -> [TariUtxo] { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_get_all_utxos(wallet.pointer, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + return result.array() + } + + func coinSplitPreview(commitments: [String], splitsCount: UInt, feePerGram: UInt64) throws -> TariCoinPreview { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let vector = TariVectorWrapper(type: TariTypeTag(0)) + try vector.add(commitments: commitments) + + let result = wallet_preview_coin_split(wallet.pointer, vector.pointer, splitsCount, feePerGram, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + return result.pointee + } + + func coinsJoinPreview(commitments: [String], feePerGram: UInt64) throws -> TariCoinPreview { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let vector = TariVectorWrapper(type: TariTypeTag(0)) + try vector.add(commitments: commitments) + + let result = wallet_preview_coin_join(wallet.pointer, vector.pointer, feePerGram, errorCodePointer) + + guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } + return result.pointee + } + + func sendTransaction(publicKey: PublicKey, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool) throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_send_transaction(wallet.pointer, publicKey.pointer, amount, nil, feePerGram, message, isOneSidedPayment, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func startRecovery(baseNodePublicKey: PublicKey, recoveredOutputMessage: String) throws -> Bool { + + let wallet = try exisingWallet + + let callback: @convention(c) (UInt8, UInt64, UInt64) -> Void = { + let status = RestoreWalletStatus(status: $0, firstValue: $1, secondValue: $2) + WalletCallbacksManager.shared.post(name: .walletRecoveryStatusUpdate, object: status) + } + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_start_recovery(wallet.pointer, baseNodePublicKey.pointer, callback, recoveredOutputMessage, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func sign(message: String) throws -> String { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_sign_message(wallet.pointer, message, errorCodePointer) + defer { string_destroy(result) } + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + + return String(cString: cString) + } + + func importExternalUtxoAsNonRewindable(amount: UInt64, spendingKey: PrivateKey, sourcePublicKey: PublicKey, metadataSignaturePointer: OpaquePointer, senderOffsetPublicKey: PublicKey, scriptPrivateKey: PrivateKey, message: String) throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_import_external_utxo_as_non_rewindable( + wallet.pointer, + amount, + spendingKey.pointer, + sourcePublicKey.pointer, + nil, + metadataSignaturePointer, + senderOffsetPublicKey.pointer, + scriptPrivateKey.pointer, + nil, + nil, + 0, + message, + errorCodePointer + ) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func commitmentSignatureCreateFromBytes(publicNonceBytes: ByteVector, uBytes: ByteVector, vBytes: ByteVector) throws -> OpaquePointer { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = commitment_signature_create_from_bytes(publicNonceBytes.pointer, uBytes.pointer, vBytes.pointer, errorCodePointer) + + guard errorCode == 0, let pointer = result else { throw WalletError(code: errorCode) } + return pointer + } + + func applyEncryption(passphrase: String) throws { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + wallet_apply_encryption(wallet.pointer, passphrase, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + } + + func removeEncryption() throws { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + wallet_remove_encryption(wallet.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + } + + func requiredConfirmationsCount() throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + + let result = wallet_get_num_confirmations_required(wallet.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func set(key: String, value: String) throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_set_key_value(wallet.pointer, key, value, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func clear(key: String) throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_clear_value(wallet.pointer, key, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func value(key: String) throws -> String { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_get_value(wallet.pointer, key, errorCodePointer) + + guard errorCode == 0, let cString = result else { throw WalletError(code: errorCode) } + return String(cString: cString) + + } + + func seedWords() throws -> SeedWords { + let wallet = try exisingWallet + return try SeedWords(walletPointer: wallet.pointer) + } + + func coinSplit(commitments: TariVectorWrapper, splitsCount: UInt, feePerGram: UInt64) throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_coin_split(wallet.pointer, commitments.pointer, splitsCount, feePerGram, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func coinJoin(commitments: TariVectorWrapper, feePerGram: UInt64) throws -> UInt64 { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_coin_join(wallet.pointer, commitments.pointer, feePerGram, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func restartTransactionBroadcast() throws -> Bool { + + let wallet = try exisingWallet + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + let result = wallet_restart_transaction_broadcast(wallet.pointer, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + return result + } + + func log(message: String) throws { + + var errorCode: Int32 = -1 + let errorCodePointer = PointerHandler.pointer(for: &errorCode) + log_debug_message(message, errorCodePointer) + + guard errorCode == 0 else { throw WalletError(code: errorCode) } + } +} diff --git a/MobileWallet/TariLib/Core/Services/CoreTariService.swift b/MobileWallet/TariLib/Core/Services/CoreTariService.swift new file mode 100644 index 00000000..ee323614 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/CoreTariService.swift @@ -0,0 +1,63 @@ +// CoreTariService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +protocol MainServiceable: AnyObject { + var transactions: TariTransactionsService { get } + var contacts: TariContactsService { get } + var validation: TariValidationService { get } + var fees: TariFeesService { get } + var connection: TariConnectionService { get } + var utxos: TariUTXOsService { get } + var recovery: TariRecoveryService { get } + var faucet: TariFaucetService { get } + var encryption: TariEncryptionService { get } + var walletBalance: TariBalanceService { get } +} + +class CoreTariService { + + unowned private(set) var walletManager: FFIWalletManager + unowned private(set) var services: MainServiceable + + init(walletManager: FFIWalletManager, services: MainServiceable) { + self.walletManager = walletManager + self.services = services + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/TorMonitor.swift b/MobileWallet/TariLib/Core/Services/TariBalanceService.swift similarity index 67% rename from MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/TorMonitor.swift rename to MobileWallet/TariLib/Core/Services/TariBalanceService.swift index 3bf1e946..50bfddc0 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/TorMonitor.swift +++ b/MobileWallet/TariLib/Core/Services/TariBalanceService.swift @@ -1,8 +1,8 @@ -// TorMonitor.swift +// TariBalanceService.swift /* Package MobileWallet - Created by Adrian Truszczynski on 18/07/2022 + Created by Adrian Truszczynski on 04/10/2022 Using Swift 5.0 Running on macOS 12.4 @@ -40,24 +40,19 @@ import Combine -final class TorMonitor { - - enum Status { - case disconnected - case connecting - case connected - case failed - } +final class TariBalanceService: CoreTariService { // MARK: - Properties - @Published private(set) var status: Status = .disconnected + @Published private(set) var balance: WalletBalance = .zero + @Published private var ffiBalance: Balance? private var cancellables = Set() - // MARK: - Initialisers + // MARK: - Initialiser - init() { + override init(walletManager: FFIWalletManager, services: MainServiceable) { + super.init(walletManager: walletManager, services: services) setupCallbacks() } @@ -65,22 +60,16 @@ final class TorMonitor { private func setupCallbacks() { - OnionManager.shared.$state - .compactMap { [weak self] in self?.map(torState: $0) } - .assign(to: \.status, on: self) + WalletCallbacksManager.shared.walletBalanceUpdatePublisher + .sink { [weak self] in self?.ffiBalance = $0 } .store(in: &cancellables) - } - - private func map(torState: OnionManager.TorState) -> Status { - switch torState { - case .none: - return .disconnected - case .started: - return .connecting - case .connected: - return .connected - case .stopped: - return .failed - } + + $ffiBalance + .compactMap { $0 } + .map { WalletBalance(available: $0.available, incoming: $0.incoming, outgoing: $0.outgoing, timeLocked: $0.timelocked) } + .assignPublisher(to: \.balance, on: self) + .store(in: &cancellables) + + ffiBalance = try? walletManager.balance() } } diff --git a/MobileWallet/TariLib/Core/Services/TariConnectionService.swift b/MobileWallet/TariLib/Core/Services/TariConnectionService.swift new file mode 100644 index 00000000..397feb2a --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariConnectionService.swift @@ -0,0 +1,64 @@ +// TariConnectionService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariConnectionService: CoreTariService { + + // MARK: - Actions + + @discardableResult func select(baseNode: BaseNode) throws -> Bool { + services.validation.reset() + do { + let result = try walletManager.addBaseNodePeer(publicKeyPointer: baseNode.publicKey.pointer, address: baseNode.address) + NetworkManager.shared.selectedNetwork.selectedBaseNode = baseNode + return result + } catch FFIWalletManager.GeneralError.unableToCreateWallet { + NetworkManager.shared.selectedNetwork.selectedBaseNode = baseNode + return false + } catch { + throw error + } + } + + func addBaseNode(name: String, peer: String) throws { + let baseNode = try BaseNode(name: name, peer: peer) + NetworkManager.shared.selectedNetwork.customBaseNodes.append(baseNode) + try select(baseNode: baseNode) + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariContactsService.swift b/MobileWallet/TariLib/Core/Services/TariContactsService.swift new file mode 100644 index 00000000..660f0645 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariContactsService.swift @@ -0,0 +1,56 @@ +// TariContactsService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariContactsService: CoreTariService { + + // MARK: - Properties + + var allContacts: [Contact] { + get throws { try walletManager.walletContacts().all } + } + + @discardableResult func upsert(contact: Contact) throws -> Bool { + try walletManager.upsert(contact: contact) + } + + func findContact(hex: String) throws -> Contact? { + try allContacts.first { try $0.publicKey.byteVector.hex == hex } + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariEncryptionService.swift b/MobileWallet/TariLib/Core/Services/TariEncryptionService.swift new file mode 100644 index 00000000..4df40d30 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariEncryptionService.swift @@ -0,0 +1,63 @@ +// TariEncryptionService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariEncryptionService: CoreTariService { + + // MARK: - Properties + + private let passphrase: String + + // MARK: - Initialiser + + init(walletManager: FFIWalletManager, services: MainServiceable, passphrase: String) { + self.passphrase = passphrase + super.init(walletManager: walletManager, services: services) + } + + // MARK: - Actions + + func apply() throws { + try walletManager.applyEncryption(passphrase: passphrase) + } + + func remove() throws { + try walletManager.removeEncryption() + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariFaucetService.swift b/MobileWallet/TariLib/Core/Services/TariFaucetService.swift new file mode 100644 index 00000000..cf0da3aa --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariFaucetService.swift @@ -0,0 +1,77 @@ +// TariFaucetService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariFaucetService: CoreTariService { + + enum InternalError: Error { + case invalidSignatureAndNonceString + } + + // MARK: - Actions + + func sign(message: String) throws -> MessageMetadata { + let data = try walletManager.sign(message: message) + let components = data.components(separatedBy: "|") + guard components.count == 2 else { throw InternalError.invalidSignatureAndNonceString } + return MessageMetadata(hex: components[0], nonce: components[1]) + } + + @discardableResult func importUtxo(amount: UInt64, spendingKey: PrivateKey, sourcePublicKey: PublicKey, metadataSignaturePointer: OpaquePointer, senderOffsetPublicKey: PublicKey, scriptPrivateKey: PrivateKey, message: String) throws -> UInt64 { + + try walletManager.importExternalUtxoAsNonRewindable( + amount: amount, + spendingKey: spendingKey, + sourcePublicKey: sourcePublicKey, + metadataSignaturePointer: metadataSignaturePointer, + senderOffsetPublicKey: senderOffsetPublicKey, + scriptPrivateKey: scriptPrivateKey, + message: message + ) + } + + func commitmentSignature(publicNonce: Data, u: Data, v: Data) throws -> OpaquePointer { + + let publicNonceBytes = try ByteVector(data: publicNonce) + let uBytes = try ByteVector(data: u) + let vBytes = try ByteVector(data: v) + + return try walletManager.commitmentSignatureCreateFromBytes(publicNonceBytes: publicNonceBytes, uBytes: uBytes, vBytes: vBytes) + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariFeesService.swift b/MobileWallet/TariLib/Core/Services/TariFeesService.swift new file mode 100644 index 00000000..41e49f81 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariFeesService.swift @@ -0,0 +1,52 @@ +// TariFeesService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariFeesService: CoreTariService { + + // MARK: - Actions + + func estimateFee(amount: UInt64, feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue, kernelsCount: UInt64 = Tari.defaultKernelCount, outputsCount: UInt64 = Tari.defaultOutputCount) throws -> UInt64 { + try walletManager.feeEstimate(amount: amount, feePerGram: feePerGram, kernelsCount: kernelsCount, outputsCount: outputsCount) + } + + func feePerGramStats(count: UInt32) throws -> TariFeePerGramStats { + try walletManager.feePerGramStats(count: count) + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift b/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift new file mode 100644 index 00000000..f6573f31 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariKeyValueService.swift @@ -0,0 +1,57 @@ +// TariKeyValueService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariKeyValueService: CoreTariService { + + enum Key: String { + case network = "SU7FM2O6Q3BU4XVN7HDD" + } + + func value(key: Key) throws -> String { + try walletManager.value(key: key.rawValue) + } + + @discardableResult func set(key: Key, value: String?) throws -> Bool { + guard let value = value else { + return try walletManager.clear(key: key.rawValue) + } + return try walletManager.set(key: key.rawValue, value: value) + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariRecoveryService.swift b/MobileWallet/TariLib/Core/Services/TariRecoveryService.swift new file mode 100644 index 00000000..81a643d2 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariRecoveryService.swift @@ -0,0 +1,59 @@ +// TariRecoveryService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariRecoveryService: CoreTariService { + + // MARK: - Properties + + var seedWords: [String] { + get throws { try walletManager.seedWords().all } + } + + // MARK: - Actions + + func startRecovery(recoveredOutputMessage: String) throws -> Bool { + let publicKey = NetworkManager.shared.selectedNetwork.selectedBaseNode.publicKey + return try walletManager.startRecovery(baseNodePublicKey: publicKey, recoveredOutputMessage: recoveredOutputMessage) + } + + func allSeedWords(forLanguage language: SeedWordsMnemonicWordList.Language) throws -> [String] { + try SeedWordsMnemonicWordList(language: language).seedWords + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariTransactionsService.swift b/MobileWallet/TariLib/Core/Services/TariTransactionsService.swift new file mode 100644 index 00000000..8d33c757 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariTransactionsService.swift @@ -0,0 +1,187 @@ +// TariTransactionsService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class TariTransactionsService: CoreTariService { + + enum InternalError: Error { + case insufficientFunds(spendableMicroTari: UInt64) + } + + // MARK: - Properties + + @Published private(set) var completed: [CompletedTransaction] = [] + @Published private(set) var cancelled: [CompletedTransaction] = [] + @Published private(set) var pendingInbound: [PendingInboundTransaction] = [] + @Published private(set) var pendingOutbound: [PendingOutboundTransaction] = [] + @Published private(set) var error: Error? + + var requiredConfirmationsCount: UInt64 { + get throws { try walletManager.requiredConfirmationsCount() } + } + + private var completedTransactions: [CompletedTransaction] { + get throws { + let transactions = try walletManager.completedTransactions() + let count = try transactions.count + return try (0..() + + // MARK: - Initialiser + + override init(walletManager: FFIWalletManager, services: MainServiceable) { + super.init(walletManager: walletManager, services: services) + fetchData() + setupCallbacks() + } + + // MARK: - Setups + + private func fetchData() { + do { + completed = try completedTransactions + cancelled = try cancelledTransactions + pendingInbound = try pendingInboundTransactions + pendingOutbound = try pendingOutboundTransactions + } catch { + self.error = error + } + } + + private func setupCallbacks() { + + WalletCallbacksManager.shared.receivedTransaction + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.receivedTransactionReply + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.receivedFinalizedTransaction + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionBroadcast + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionMined + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.unconfirmedTransactionMined + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.fauxTransactionConfirmed + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.fauxTransactionUnconfirmed + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionSendResult + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionCancellation + .sink { [weak self] _ in self?.fetchData() } + .store(in: &cancellables) + } + + // MARK: - Actions + + func cancelPendingTransaction(identifier: UInt64) throws -> Bool { + try walletManager.cancelPendingTransaction(identifier: identifier) + } + + func send(toPublicKey publicKey: PublicKey, amount: UInt64, feePerGram: UInt64, message: String, isOneSidedPayment: Bool, + kernelsCount: UInt64 = Tari.defaultKernelCount, outputsCount: UInt64 = Tari.defaultOutputCount) throws -> UInt64 { + + let estimatedFee = try walletManager.feeEstimate(amount: amount, feePerGram: feePerGram, kernelsCount: kernelsCount, outputsCount: outputsCount) + let total = estimatedFee + amount + let availableBalance = services.walletBalance.balance.available + + guard availableBalance >= total else { + throw InternalError.insufficientFunds(spendableMicroTari: availableBalance) + } + + return try walletManager.sendTransaction(publicKey: publicKey, amount: amount, feePerGram: feePerGram, message: message, isOneSidedPayment: isOneSidedPayment) + } +} + +extension TariTransactionsService { + + var onUpdate: AnyPublisher { + Publishers.CombineLatest4($completed, $cancelled, $pendingInbound, $pendingOutbound) + .map { _ in Void() } + .eraseToAnyPublisher() + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariUTXOsService.swift b/MobileWallet/TariLib/Core/Services/TariUTXOsService.swift new file mode 100644 index 00000000..366ea2bd --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariUTXOsService.swift @@ -0,0 +1,70 @@ +// TariUTXOsService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class TariUTXOsService: CoreTariService { + + // MARK: - Properties + + var allUtxos: [TariUtxo] { + get throws { try walletManager.utxos() } + } + + // MARK: - Actions + + func coinBreakPreview(commitments: [String], splitsCount: UInt, feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue) throws -> TariCoinPreview { + try walletManager.coinSplitPreview(commitments: commitments, splitsCount: splitsCount, feePerGram: feePerGram) + } + + func combineCoinsPreview(commitments: [String], feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue) throws -> TariCoinPreview { + try walletManager.coinsJoinPreview(commitments: commitments, feePerGram: feePerGram) + } + + func breakCoins(commitments: [String], splitsCount: UInt, feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue) throws { + let commitmentsVector = TariVectorWrapper(type: TariTypeTag(0)) + try commitmentsVector.add(commitments: commitments) + _ = try walletManager.coinSplit(commitments: commitmentsVector, splitsCount: splitsCount, feePerGram: feePerGram) + } + + func combineCoins(commitments: [String], feePerGram: UInt64 = Tari.defaultFeePerGram.rawValue) throws { + let commitmentsVector = TariVectorWrapper(type: TariTypeTag(0)) + try commitmentsVector.add(commitments: commitments) + _ = try walletManager.coinJoin(commitments: commitmentsVector, feePerGram: feePerGram) + } +} diff --git a/MobileWallet/TariLib/Core/Services/TariValidationService.swift b/MobileWallet/TariLib/Core/Services/TariValidationService.swift new file mode 100644 index 00000000..9b1c8517 --- /dev/null +++ b/MobileWallet/TariLib/Core/Services/TariValidationService.swift @@ -0,0 +1,124 @@ +// TariValidationService.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 04/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class TariValidationService: CoreTariService { + + enum SyncStatus { + case idle + case syncing + case synced + case failed + } + + private enum TransactionType { + case txo + case tx + } + + // MARK: - Properties + + @Published private(set) var status: SyncStatus = .idle + + private var unverifiedTransactions: [TransactionType: UInt64] = [:] + private var cancellables = Set() + + // MARK: - Initialiser + + override init(walletManager: FFIWalletManager, services: MainServiceable) { + super.init(walletManager: walletManager, services: services) + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + WalletCallbacksManager.shared.transactionOutputValidation + .filter { $0.status != .alreadyBusy } + .map { ($0.identifier, $0.status == .success) } + .sink { [weak self] in self?.handleTransactionValidation(type: .txo, identifier: $0, isSuccess: $1) } + .store(in: &cancellables) + + WalletCallbacksManager.shared.transactionValidation + .sink { [weak self] in self?.handleTransactionValidation(type: .tx, identifier: $0.identifier, isSuccess: $0.isSuccess) } + .store(in: &cancellables) + } + + // MARK: - Actions + + func reset() { + unverifiedTransactions.removeAll() + status = .idle + } + + func sync() throws { + unverifiedTransactions[.txo] = try walletManager.startTransactionOutputValidation() + unverifiedTransactions[.tx] = try walletManager.startTransactionValidation() + status = .syncing + } + + // MARK: - Handlers + + private func handleTransactionValidation(type: TransactionType, identifier: UInt64, isSuccess: Bool) { + + guard unverifiedTransactions[type] == identifier else { return } + + guard isSuccess else { + unverifiedTransactions.removeAll() + status = .failed + return + } + + unverifiedTransactions[type] = nil + + if type == .tx { + restartTransactionBroadcast() + } + + guard unverifiedTransactions.isEmpty else { return } + status = .synced + } + + private func restartTransactionBroadcast() { + _ = try? walletManager.restartTransactionBroadcast() + } +} diff --git a/MobileWallet/TariLib/Core/Tari.swift b/MobileWallet/TariLib/Core/Tari.swift new file mode 100644 index 00000000..a883afe9 --- /dev/null +++ b/MobileWallet/TariLib/Core/Tari.swift @@ -0,0 +1,296 @@ +// Tari.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 19/11/2021 + Using Swift 5.0 + Running on macOS 12.0 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine +import UIKit + +final class Tari: MainServiceable { + + // MARK: - Constants + + static let defaultFeePerGram = MicroTari(10) + static let defaultKernelCount = UInt64(1) + static let defaultOutputCount = UInt64(2) + + let databaseName = "tari_wallet" + var connectedDatabaseDirectory: URL { TariSettings.storageDirectory.appendingPathComponent("\(databaseName)_\(NetworkManager.shared.selectedNetwork.name)", isDirectory: true) } + + private let logFilePrefix = "log" + private let publicAddress = "/ip4/0.0.0.0/tcp/9838" + private let discoveryTimeoutSec: UInt64 = 20 + private let safMessageDurationSec: UInt64 = 10800 + + // MARK: - Properties + + static let shared = Tari() + + let connectionMonitor = ConnectionMonitor() + + private(set) lazy var connection = TariConnectionService(walletManager: walletManager, services: self) + private(set) lazy var contacts = TariContactsService(walletManager: walletManager, services: self) + private(set) lazy var encryption = TariEncryptionService(walletManager: walletManager, services: self, passphrase: passphrase) + private(set) lazy var faucet = TariFaucetService(walletManager: walletManager, services: self) + private(set) lazy var fees = TariFeesService(walletManager: walletManager, services: self) + private(set) lazy var keyValues = TariKeyValueService(walletManager: walletManager, services: self) + private(set) lazy var recovery = TariRecoveryService(walletManager: walletManager, services: self) + private(set) lazy var transactions = TariTransactionsService(walletManager: walletManager, services: self) + private(set) lazy var utxos = TariUTXOsService(walletManager: walletManager, services: self) + private(set) lazy var validation = TariValidationService(walletManager: walletManager, services: self) + private(set) lazy var walletBalance = TariBalanceService(walletManager: walletManager, services: self) + + private(set) lazy var logFilePath: String = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss" + let dateString = dateFormatter.string(from: Date()) + return "\(TariSettings.storageDirectory.path)/\(logFilePrefix)-\(dateString).txt" + }() + + var walletPublicKey: PublicKey { + get throws { try walletManager.walletPublicKey() } + } + + var logsURLs: [URL] { + get throws { + try FileManager.default.contentsOfDirectory(at: TariSettings.storageDirectory, includingPropertiesForKeys: nil) + .filter { $0.lastPathComponent.hasPrefix(logFilePrefix) } + .sorted { $0.path > $1.path } + } + } + + var isWalletExist: Bool { (try? connectedDatabaseDirectory.checkResourceIsReachable()) ?? false } + var torBridgesConfiguration: BridgesConfiguration { torManager.usedBridgesConfiguration } + + var canAutomaticalyReconnectWallet: Bool = false + + private let torManager = TorManager() + private let walletManager = FFIWalletManager() + private var cancellables = Set() + + private var passphrase: String { + guard let passphrase = AppKeychainWrapper.dbPassphrase else { + let newPassphrase = String.random(length: 32) + AppKeychainWrapper.dbPassphrase = newPassphrase + return newPassphrase + } + return passphrase + } + + func update(torBridgesConfiguration: BridgesConfiguration) async throws { + try await torManager.update(bridgesConfiguration: torBridgesConfiguration) + } + + // MARK: - Initialisers + + private init() { + connectionMonitor.setupPublishers( + torConnectionStatus: torManager.$connectionStatus.eraseToAnyPublisher(), + torBootstrapProgress: torManager.$bootstrapProgress.eraseToAnyPublisher(), + baseNodeConnectionStatus: walletManager.$baseNodeConnectionStatus.eraseToAnyPublisher(), + baseNodeSyncStatus: validation.$status.eraseToAnyPublisher() + ) + setupCallbacks() + } + + // MARK: - Setups + + private func setupCallbacks() { + + Publishers.CombineLatest(connectionMonitor.$baseNodeConnection, connectionMonitor.$syncStatus) + .filter { $0 == .offline || $1 == .failed } + .sink { [weak self] _, _ in + try? self?.switchBaseNode() + } + .store(in: &cancellables) + + walletManager.$baseNodeConnectionStatus + .sink { [weak self] in + switch $0 { + case .offline: + self?.validation.reset() + case .connecting: + break + case .online: + try? self?.validation.sync() + } + } + .store(in: &cancellables) + + NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) + .sink { [weak self] _ in self?.connect() } + .store(in: &cancellables) + + NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) + .sink { [weak self] _ in self?.disconnect() } + .store(in: &cancellables) + } + // MARK: - Actions + + func startWallet() async throws { + await waitForTor() + try startWallet(seedWords: nil) + } + + func restoreWallet(seedWords: [String]) throws { + try startWallet(seedWords: seedWords) + } + + func deleteWallet() { + disconnectWallet() + try? deleteWalletDirectory() + try? deleteLogs() + } + + func select(network: TariNetwork) { + disconnectWallet() + NetworkManager.shared.selectedNetwork = network + } + + func log(message: String) { + try? walletManager.log(message: message) + } + + private func connect() { + Task { + try? await torManager.reinitiateConnection() + guard canAutomaticalyReconnectWallet, !walletManager.isWalletConnected else { return } + try? await startWallet() + } + } + + private func disconnect() { + walletManager.disconnectWallet() + Task { await torManager.stop() } + } + + private func startWallet(seedWords: [String]?) throws { + + let commsConfig = try makeCommsConfig() + let selectedNetwork = NetworkManager.shared.selectedNetwork + var walletSeedWords: SeedWords? + + if let seedWords = seedWords { + walletSeedWords = try SeedWords(words: seedWords) + } + + if !isWalletExist { + try createWalletDirectory() + } + + do { + try walletManager.connectWallet(commsConfig: commsConfig, logFilePath: logFilePath, seedWords: walletSeedWords, passphrase: passphrase, networkName: selectedNetwork.name) + } catch { + guard let error = error as? WalletError, error == WalletError.invalidPassphrase else { throw error } + try walletManager.connectWallet(commsConfig: commsConfig, logFilePath: logFilePath, seedWords: walletSeedWords, passphrase: nil, networkName: selectedNetwork.name) + try Tari.shared.encryption.apply() + } + } + + private func waitForTor() async { + return await withCheckedContinuation { continuation in + Tari.shared.connectionMonitor.$torConnection + .filter { $0 == .portsOpen || $0 == .connected } + .first() + .sink { _ in continuation.resume() } + .store(in: &cancellables) + } + } + + private func disconnectWallet() { + walletManager.disconnectWallet() + UserDefaults.standard.removeAll() + NetworkManager.shared.removeSelectedNetworkSettings() + } + + private func makeCommsConfig() throws -> CommsConfig { + + let torCookie = try torManager.cookie() + let transportType = try makeTransportType(torCookie: torCookie) + + return try CommsConfig( + publicAddress: publicAddress, + transport: transportType, + databaseName: databaseName, + databaseFolderPath: connectedDatabaseDirectory.path, + discoveryTimeoutInSecs: discoveryTimeoutSec, + safMessageDurationInSec: safMessageDurationSec + ) + } + + private func makeTransportType(torCookie: Data) throws -> TransportConfig { + + let torCookie = try ByteVector(data: torCookie) + + return try TransportConfig( + controlServerAddress: torManager.controlServerAddress, + torPort: 18101, + torCookie: torCookie, + socksUsername: nil, + socksPassword: nil + ) + } + + private func createWalletDirectory() throws { + try FileManager.default.createDirectory(at: connectedDatabaseDirectory, withIntermediateDirectories: true, attributes: nil) + } + + private func deleteWalletDirectory() throws { + try FileManager.default.removeItem(at: connectedDatabaseDirectory) + } + + private func deleteLogs() throws { + try FileManager.default.contentsOfDirectory(at: TariSettings.storageDirectory, includingPropertiesForKeys: nil) + .filter { $0.lastPathComponent.contains(logFilePrefix) } + .forEach { try FileManager.default.removeItem(at: $0) } + } + + private func switchBaseNode() throws { + + let selectedBaseNode = NetworkManager.shared.selectedNetwork.selectedBaseNode + guard NetworkManager.shared.selectedNetwork.baseNodes.contains(selectedBaseNode), NetworkManager.shared.selectedNetwork.baseNodes.count > 1 else { return } + + var newBaseNode: BaseNode + + repeat { + newBaseNode = try NetworkManager.shared.selectedNetwork.randomNode() + } while newBaseNode == selectedBaseNode + + try connection.select(baseNode: newBaseNode) + } +} diff --git a/MobileWallet/TariLib/Tor/Helpers/Ipv6Tester.swift b/MobileWallet/TariLib/Core/Tor/Helpers/Ipv6Tester.swift similarity index 100% rename from MobileWallet/TariLib/Tor/Helpers/Ipv6Tester.swift rename to MobileWallet/TariLib/Core/Tor/Helpers/Ipv6Tester.swift diff --git a/MobileWallet/TariLib/Tor/Helpers/NetworkTools.h b/MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.h similarity index 100% rename from MobileWallet/TariLib/Tor/Helpers/NetworkTools.h rename to MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.h diff --git a/MobileWallet/TariLib/Tor/Helpers/NetworkTools.m b/MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.m similarity index 100% rename from MobileWallet/TariLib/Tor/Helpers/NetworkTools.m rename to MobileWallet/TariLib/Core/Tor/Helpers/NetworkTools.m diff --git a/MobileWallet/TariLib/Tor/OnionSettings.swift b/MobileWallet/TariLib/Core/Tor/OnionSettings.swift similarity index 88% rename from MobileWallet/TariLib/Tor/OnionSettings.swift rename to MobileWallet/TariLib/Core/Tor/OnionSettings.swift index f00eaeef..ac67d926 100644 --- a/MobileWallet/TariLib/Tor/OnionSettings.swift +++ b/MobileWallet/TariLib/Core/Tor/OnionSettings.swift @@ -38,9 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - -@objc class BridgesConfuguration: NSObject, Codable { +final class BridgesConfiguration: NSObject, Codable { enum CodingKeys: CodingKey { case bridges, customBridges @@ -68,7 +66,7 @@ import Foundation } } -class OnionSettings: NSObject { +final class OnionSettings: NSObject { static let torBridgesLink = URL(string: "https://bridges.torproject.org/bridges")! enum BridgesType: Int { @@ -81,9 +79,9 @@ class OnionSettings: NSObject { case current = "use_bridges" } - static let defaultBridgesConfiguration: BridgesConfuguration = BridgesConfuguration(bridges: .none, customBridges: nil) + static let defaultBridgesConfiguration: BridgesConfiguration = BridgesConfiguration(bridges: .none, customBridges: nil) - class var backupBridgesConfiguration: BridgesConfuguration { + class var backupBridgesConfiguration: BridgesConfiguration { get { return getBridgesConfiguration(for: .backup) } @@ -92,7 +90,7 @@ class OnionSettings: NSObject { } } - class var currentlyUsedBridgesConfiguration: BridgesConfuguration { + class var currentlyUsedBridgesConfiguration: BridgesConfiguration { get { return getBridgesConfiguration(for: .current) } @@ -101,17 +99,17 @@ class OnionSettings: NSObject { } } - private class func setBridgesConfiguration(_ configuration: BridgesConfuguration, for key: BridgesConfigurationKey) { + private class func setBridgesConfiguration(_ configuration: BridgesConfiguration, for key: BridgesConfigurationKey) { let encoder = JSONEncoder() if let encoded = try? encoder.encode(configuration) { UserDefaults.standard.set(encoded, forKey: key.rawValue) } } - private class func getBridgesConfiguration(for key: BridgesConfigurationKey) -> BridgesConfuguration { + private class func getBridgesConfiguration(for key: BridgesConfigurationKey) -> BridgesConfiguration { let decoder = JSONDecoder() if let configurationData = UserDefaults.standard.object(forKey: key.rawValue) as? Data, - let decodedConfiguration = try? decoder.decode(BridgesConfuguration.self, from: configurationData) { + let decodedConfiguration = try? decoder.decode(BridgesConfiguration.self, from: configurationData) { return decodedConfiguration } return defaultBridgesConfiguration diff --git a/MobileWallet/TariLib/Core/TorManager.swift b/MobileWallet/TariLib/Core/TorManager.swift new file mode 100644 index 00000000..407102c5 --- /dev/null +++ b/MobileWallet/TariLib/Core/TorManager.swift @@ -0,0 +1,387 @@ +// TorManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 07/01/2022 + Using Swift 5.0 + Running on macOS 12.1 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Tor +import Combine +import IPtProxy + +final class TorManager { + + enum ConnectionStatus { + case disconnected + case connecting + case portsOpen + case connected + case disconnecting + } + + enum TorError: Error { + case connectionFailed(error: Error) + case connectionTimeout + case missingCookie(error: Error?) + } + + // MARK: - Constants + + + private let controlAddress = "127.0.0.1" + private let controlPort: UInt16 = 39069 + private let dataDirectoryUrl = TariSettings.storageDirectory.appendingPathComponent("tor", isDirectory: true) + private lazy var authDirectoryURL = dataDirectoryUrl.appendingPathComponent("auth", isDirectory: true) + private(set) lazy var controlServerAddress = "/ip4/\(controlAddress)/tcp/\(controlPort)" + + // MARK: - Properties + + @Published private(set) var connectionStatus: ConnectionStatus = .disconnected + @Published private(set) var bootstrapProgress: Int = 0 + @Published private(set) var error: Error? + + private var controller: TorController? + + private var configuration: TorConfiguration? + private var torThread: TorThread? + private var retryAction: DispatchWorkItem? + private var needsReconfiguration = false + + private(set) var usedBridgesConfiguration: BridgesConfiguration = OnionSettings.currentlyUsedBridgesConfiguration + + private var backupBridgesConfiguration: BridgesConfiguration { + get { OnionSettings.backupBridgesConfiguration } + set { OnionSettings.backupBridgesConfiguration = newValue } + } + + private var currentBridgesConfiguration: BridgesConfiguration { + get { OnionSettings.currentlyUsedBridgesConfiguration } + set { OnionSettings.currentlyUsedBridgesConfiguration = newValue } + } + + // MARK: - Initialisers + + init() { + configuration = try? createBaseConfiguration() + } + + // MARK: - Actions + + func update(bridgesConfiguration: BridgesConfiguration) async throws { + updateAndValidate(bridgesConfiguration: bridgesConfiguration) + try await reinitiateConnection() + OnionSettings.backupBridgesConfiguration = bridgesConfiguration + OnionSettings.currentlyUsedBridgesConfiguration = bridgesConfiguration + } + + func reinitiateConnection() async throws { + await stop() + try await start() + } + + func stop() async { + controller?.disconnect() + torThread?.cancel() + connectionStatus = .disconnecting + await waitForTorThreadStop() + connectionStatus = .disconnected + bootstrapProgress = 0 + } + + private func start() async throws { + + connectionStatus = .connecting + controller = TorController(socketHost: controlAddress, port: controlPort) + + if torThread?.isCancelled ?? true { + try configureSession() + } else if needsReconfiguration { + reconfigureSession() + } + + return try await withCheckedThrowingContinuation { continuation in + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + guard let self = self else { return } + do { + try self.startController() + try self.observeAuthentication() + self.setupRetry() + continuation.resume() + } catch { + continuation.resume(throwing: error) + } + } + } + } + + private func configureSession() throws { + + let configuration = try createBaseConfiguration() + var arguments = configuration.arguments ?? [] + + arguments += bridgesArguments() + + switch Ipv6Tester.ipv6_status() { + case .torIpv6ConnOnly: + arguments += ["--ClientPreferIPv6ORPort", "1"] + if (usedBridgesConfiguration.bridgesType != .none) { + arguments += ["--ClientUseIPv4", "1"] + } else { + arguments += ["--ClientUseIPv4", "0"] + } + case .torIpv6ConnDual, .torIpv6ConnFalse, .torIpv6ConnUnknown: + arguments += [ + "--ClientPreferIPv6ORPort", "auto", + "--ClientUseIPv4", "1", + ] + } + + configuration.arguments = arguments + + torThread = TorThread(configuration: configuration) + needsReconfiguration = false + torThread?.start() + startIObfs4Proxy() + } + + private func waitForTorThreadStop() async { + + return await withCheckedContinuation { [weak self] continuation in + + guard self?.torThread != nil else { + continuation.resume() + return + } + + DispatchQueue.main.async { [weak self] in + Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { + guard self?.torThread == nil || (self?.torThread?.isFinished == true && self?.torThread?.isExecuting == false) else { return } + $0.invalidate() + self?.torThread = nil + continuation.resume() + } + } + } + } + + private func startController() throws { + + guard let controller = controller else { return } + + if !controller.isConnected { + do { + try controller.connect() + } catch { + throw TorError.connectionFailed(error: error) + } + } + } + + private func setupRetry() { + + let retryAction = DispatchWorkItem { [weak self] in + self?.controller?.setConfForKey("DisableNetwork", withValue: "1") + self?.controller?.setConfForKey("DisableNetwork", withValue: "0") + + self?.error = TorError.connectionTimeout + } + + self.retryAction = retryAction + DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: retryAction) + } + + private func cancelRetry() { + retryAction?.cancel() + retryAction = nil + } + + private func startIObfs4Proxy() { + IPtProxyStartObfs4Proxy() + } + + private func updateAndValidate(bridgesConfiguration: BridgesConfiguration) { + + defer { + usedBridgesConfiguration = bridgesConfiguration + } + + let currentConfiguration = self.usedBridgesConfiguration + + guard currentConfiguration.bridgesType == bridgesConfiguration.bridgesType else { + needsReconfiguration = true + return + } + + let currentCustomBridges = currentConfiguration.customBridges + let customBridges = bridgesConfiguration.customBridges + + guard let currentCustomBridges = currentCustomBridges, let customBridges = customBridges else { + needsReconfiguration = (currentCustomBridges == nil && customBridges != nil) || (currentCustomBridges != nil && customBridges == nil) + return + } + + needsReconfiguration = currentCustomBridges != customBridges + } + + private func reconfigureSession() { + + let config = createBridgesConfig() + controller?.resetConf(forKey: "Bridge") + + guard !config.isEmpty else { + controller?.setConfForKey("UseBridges", withValue: "0") + return + } + + controller?.setConfs(config) + controller?.setConfForKey("UseBridges", withValue: "1") + } + + private func bridges() -> [String] { + switch usedBridgesConfiguration.bridgesType { + case .custom: + return usedBridgesConfiguration.customBridges ?? [] + default: + return [] + } + } + + private func bridgesArguments() -> [String] { + + var arguments = bridges().flatMap { ["--Bridge", $0] } + + if !arguments.isEmpty { + arguments += ["--UseBridges", "1"] + } + + return arguments + } + + private func createBridgesConfig() -> [[String: String]] { + bridges().map { ["key": "Bridge", "value": "\"\($0)\""] } + } + + // MARK: - Observers + + private func observeAuthentication() throws { + + let cookie = try cookie() + + controller?.authenticate(with: cookie) { [weak self] isSuccess, error in + guard isSuccess else { return } + self?.connectionStatus = .portsOpen + self?.observeCircuit() + self?.observeStatusEvents() + } + } + + private func observeCircuit() { + var observer: Any? + observer = controller?.addObserver { [weak self] isCircuitEstablished in + guard let self = self, isCircuitEstablished else { return } + self.connectionStatus = .connected + self.controller?.removeObserver(observer) + self.cancelRetry() + } + } + + func observeStatusEvents() { + var observer: Any? + observer = controller?.addObserver { [weak self] type, severity, action, arguments in + guard type == "STATUS_CLIENT", action == "BOOTSTRAP", let rawProgress = arguments?["PROGRESS"], let progress = Int(rawProgress) else { return false } + self?.bootstrapProgress = progress + guard progress >= 100 else { return true } + self?.controller?.removeObserver(observer) + return true + } + } + + // MARK: - Constructors + + func cookie() throws -> Data { + guard let fileUrl = configuration?.dataDirectory?.appendingPathComponent("control_auth_cookie") else { throw TorError.missingCookie(error: nil) } + do { + return try Data(contentsOf: fileUrl) + } catch { + throw TorError.missingCookie(error: error) + } + } + + private func createBaseConfiguration() throws -> TorConfiguration { + + try createDataDirectoryInNeeded() + try createAuthDirectoryInNeeded() + + let configuration = TorConfiguration() + + configuration.cookieAuthentication = true + configuration.dataDirectory = dataDirectoryUrl + + #if DEBUG + let log_loc = "notice stdout" + #else + let log_loc = "notice file /dev/null" + #endif + + configuration.arguments = [ + "--allow-missing-torrc", + "--ignore-missing-torrc", + "--clientonly", "1", + "--AvoidDiskWrites", "1", + "--socksport", "39059", + "--controlport", "\(controlAddress):\(controlPort)", + "--log", log_loc, + "--clientuseipv6", "1", + "--ClientTransportPlugin", "obfs4 socks5 127.0.0.1:47351", + "--ClientTransportPlugin", "meek_lite socks5 127.0.0.1:47352", + "--ClientOnionAuthDir", authDirectoryURL.path + ] + + return configuration + } + + // MARK: - Helpers + + private func createDataDirectoryInNeeded() throws { + guard !FileManager.default.fileExists(atPath: dataDirectoryUrl.path) else { return } + try FileManager.default.createDirectory(at: dataDirectoryUrl, withIntermediateDirectories: true) + } + + private func createAuthDirectoryInNeeded() throws { + guard !FileManager.default.fileExists(atPath: authDirectoryURL.path) else { return } + try FileManager.default.createDirectory(at: authDirectoryURL, withIntermediateDirectories: true) + } +} diff --git a/MobileWallet/TariLib/Core/WalletCallbacksManager.swift b/MobileWallet/TariLib/Core/WalletCallbacksManager.swift new file mode 100644 index 00000000..8f0cc8d4 --- /dev/null +++ b/MobileWallet/TariLib/Core/WalletCallbacksManager.swift @@ -0,0 +1,219 @@ +// WalletCallbacksManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 02/10/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine +import Foundation + +final class WalletCallbacksManager { + + // MARK: - Wallet Callbacks + + let receivedTransaction: AnyPublisher = { + NotificationCenter.default + .publisher(for: .receivedTransaction) + .compactMap { $0.object as? OpaquePointer } + .map { PendingInboundTransaction(pointer: $0) } + .share() + .eraseToAnyPublisher() + }() + + let receivedTransactionReply: AnyPublisher = { + NotificationCenter.default + .publisher(for: .receivedTransactionReply) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let receivedFinalizedTransaction: AnyPublisher = { + NotificationCenter.default + .publisher(for: .receivedFinalizedTransaction) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let transactionBroadcast: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionBroadcast) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let transactionMined: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionMined) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let unconfirmedTransactionMined: AnyPublisher = { + NotificationCenter.default + .publisher(for: .unconfirmedTransactionMined) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let fauxTransactionConfirmed: AnyPublisher = { + NotificationCenter.default + .publisher(for: .fauxTransactionConfirmed) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let fauxTransactionUnconfirmed: AnyPublisher = { + NotificationCenter.default + .publisher(for: .fauxTransactionUnconfirmed) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: false) } + .share() + .eraseToAnyPublisher() + }() + + let transactionSendResult: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionSendResult) + .compactMap { $0.object as? TransactionSendResult } + .share() + .eraseToAnyPublisher() + }() + + let transactionCancellation: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionCancellation) + .compactMap { $0.object as? OpaquePointer } + .map { CompletedTransaction(pointer: $0, isCancelled: true) } + .share() + .eraseToAnyPublisher() + }() + + let transactionOutputValidation: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionOutputValidation) + .compactMap { $0.object as? TransactionOutputValidationData } + .share() + .eraseToAnyPublisher() + }() + + let walletBalanceUpdatePublisher: AnyPublisher = { + NotificationCenter.default + .publisher(for: .walletBalanceUpdate) + .compactMap { $0.object as? Balance } + .share() + .eraseToAnyPublisher() + }() + + let transactionValidation: AnyPublisher = { + NotificationCenter.default + .publisher(for: .transactionValidation) + .compactMap { $0.object as? TransactionValidationData } + .share() + .eraseToAnyPublisher() + }() + + let baseNodeConnectionStatus: AnyPublisher = { + NotificationCenter.default + .publisher(for: .baseNodeConnectionStatusUpdate) + .compactMap { $0.object as? BaseNodeConnectivityStatus } + .share() + .eraseToAnyPublisher() + }() + + // MARK: - Recovery Callbacks + + let walletRecoveryStatusUpdate: AnyPublisher = { + NotificationCenter.default + .publisher(for: .walletRecoveryStatusUpdate) + .compactMap { $0.object as? RestoreWalletStatus } + .share() + .eraseToAnyPublisher() + }() + + // MARK: - Properties + + static let shared: WalletCallbacksManager = WalletCallbacksManager() + private let queue = DispatchQueue(label: "com.tari.events", attributes: []) + + // MARK: - Initialisers + + private init() {} + + // MARK: - Actions + + func post(name: Notification.Name, object: Any?) { + queue.async { + NotificationCenter.default.post(name: name, object: object) + } + } +} + +extension Notification.Name { + + // MARK: - Wallet Callbacks + + static let receivedTransaction = Self(rawValue: "com.tari.wallet.received_transaction") + static let receivedTransactionReply = Self(rawValue: "com.tari.wallet.received_transaction_replay") + static let receivedFinalizedTransaction = Self(rawValue: "com.tari.wallet.received_finalized_transaction") + static let transactionBroadcast = Self(rawValue: "com.tari.wallet.transaction_broadcast") + static let transactionMined = Self(rawValue: "com.tari.wallet.transaction_mined") + static let unconfirmedTransactionMined = Self(rawValue: "com.tari.wallet.unconfirmed_transaction_mined") + static let fauxTransactionConfirmed = Self(rawValue: "com.tari.wallet.faux_transaction_confirmed") + static let fauxTransactionUnconfirmed = Self(rawValue: "com.tari.wallet.faux_transaction_unconfirmed") + static let transactionSendResult = Self(rawValue: "com.tari.wallet.transaction_send_result") + static let transactionCancellation = Self(rawValue: "com.tari.wallet.transaction_cancellation") + static let transactionOutputValidation = Self(rawValue: "com.tari.wallet.transaction_output_validation") + static let walletBalanceUpdate = Self(rawValue: "com.tari.wallet.balance_update") + static let transactionValidation = Self(rawValue: "com.tari.wallet.transaction_validation") + static let baseNodeConnectionStatusUpdate = Self(rawValue: "com.tari.wallet.base_node_connection_status_update") + + // MARK: - Recovery Callbacks + + static let walletRecoveryStatusUpdate = Self(rawValue: "com.tari.recovery.status_update") +} diff --git a/MobileWallet/TariLib/TariLib.swift b/MobileWallet/TariLib/TariLib.swift deleted file mode 100644 index e18b817f..00000000 --- a/MobileWallet/TariLib/TariLib.swift +++ /dev/null @@ -1,338 +0,0 @@ -// TariLib.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/12 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation -import Combine - -enum TariLibErrors: Error { - case saveToKeychain -} - -class TariLib { - - private let databaseNamespace = "tari_wallet" - - static let shared = TariLib() - static let logFilePrefix = "log" - var torPortsOpened = false - - var walletState: WalletState { walletStateSubject.value } - var walletStatePublisher: AnyPublisher { walletStateSubject.share().eraseToAnyPublisher() } - private let walletStateSubject = CurrentValueSubject(.notReady) - private var cancelables = Set() - - @Published private(set) var baseNodeConnectionStatus: BaseNodeConnectivityStatus = .offline - private var cancellables = Set() - - - var connectedDatabaseName: String { databaseNamespace } - var connectedDatabaseDirectory: URL { TariSettings.storageDirectory.appendingPathComponent("\(databaseNamespace)_\(NetworkManager.shared.selectedNetwork.name)", isDirectory: true) } - - enum KeyValueStorageKeys: String { - case network = "SU7FM2O6Q3BU4XVN7HDD" - } - - enum WalletState: Equatable { - static func == (lhs: TariLib.WalletState, rhs: TariLib.WalletState) -> Bool { - switch (lhs, rhs) { - case (.notReady, .notReady), (.starting, .starting), (.started, .started), (.startFailed, .startFailed): - return true - default: - return false - } - } - - case notReady - case starting - case startFailed(error: WalletError) - case started - } - - lazy var logFilePath: String = { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss" - let dateString = dateFormatter.string(from: Date()) - return "\(TariSettings.storageDirectory.path)/\(TariLib.logFilePrefix)-\(dateString).txt" - }() - - var allLogFiles: [URL] { - do { - let allLogFiles = try FileManager.default.contentsOfDirectory( - at: TariSettings.storageDirectory, - includingPropertiesForKeys: nil - ).filter({$0.lastPathComponent.contains(TariLib.logFilePrefix)}).sorted(by: { (a, b) -> Bool in - return a.path > b.path - }) - return allLogFiles - } catch { - return [] - } - } - - private let publicAddress: String = "/ip4/0.0.0.0/tcp/9838" - private let listenerAddress: String = "/ip4/0.0.0.0/tcp/9838" - - var tariWallet: Wallet? - var walletPublicKeyHex: String? // we need a cache of this for function that run while tariWallet = nil - - var isWalletExist: Bool { - do { - return try connectedDatabaseDirectory.checkResourceIsReachable() - } catch { - TariLogger.warn("Database path not reachable. Assuming wallet doesn't exist.", error: error) - return false - } - } - - var commsConfig: CommsConfig? { - do { - return try CommsConfig( - transport: try transportType(), - databaseFolderPath: connectedDatabaseDirectory.path, - databaseName: connectedDatabaseName, - publicAddress: publicAddress, - discoveryTimeoutSec: TariSettings.shared.discoveryTimeoutSec, - safMessageDurationSec: TariSettings.shared.safMessageDurationSec - ) - } catch { - TariLogger.error("Failed to create comms config", error: error) - return nil - } - } - - private init() { - setupListeners() - } - - private func transportType() throws -> TransportConfig { - let torCookieBytes = [UInt8](try OnionManager.getCookie()) - let torCookie = try ByteVector(byteArray: torCookieBytes) - - return try TransportConfig( - controlServerAddress: "/ip4/\(OnionManager.CONTROL_ADDRESS)/tcp/\(OnionManager.CONTROL_PORT)", - torPort: 18101, - torCookie: torCookie, - socksUsername: "", - socksPassword: "" - ) - } - - private func setupListeners() { - OnionConnector.shared.addObserver(self) - walletStatePublisher - .sink { TariEventBus.postToMainThread(.walletStateChanged, sender: $0) } - .store(in: &cancelables) - - TariEventBus.events(forType: .connectionStatusChanged) - .compactMap { $0.object as? BaseNodeConnectivityStatus } - .assign(to: \.baseNodeConnectionStatus, on: self) - .store(in: &cancellables) - } - - func startTor() { - guard !TariSettings.shared.isUnitTesting else { - TariLogger.verbose("Ignoring tor start for unit tests") - return - } - guard OnionConnector.shared.connectionState != .started - && OnionConnector.shared.connectionState != .connected else { - return - } - OnionConnector.shared.start() - TariEventBus.postToMainThread(.torConnectionProgress, sender: Int(0)) - } - - func stopTor() { - guard !TariSettings.shared.isUnitTesting else { - TariLogger.verbose("Ignoring tor stop for unit tests") - return - } - OnionConnector.shared.stop() - torPortsOpened = false - } - - private func startListeningToBaseNodeSync() { - TariEventBus.onBackgroundThread(self, eventType: .baseNodeSyncComplete) { [weak self] result in - guard let result = result?.object as? [String: Any], let isSuccess = result["success"] as? Bool, isSuccess else { return } - - do { - try self?.tariWallet?.cancelAllExpiredPendingTx() - TariLogger.verbose("Checked for expired pending transactions") - } catch { - TariLogger.error("Failed to cancel expired pending transactions", error: error) - } - } - } - - /// Starts an existing wallet service. Must only be called if wallet DB files already exist. - func startWallet(seedWords: SeedWords?) { - walletStateSubject.send(.starting) - guard let config = commsConfig else { - walletStateSubject.send(.startFailed(error: .unknown)) - return - } - let loggingFilePath = TariLib.shared.logFilePath - do { - tariWallet = try Wallet(commsConfig: config, loggingFilePath: loggingFilePath, seedWords: seedWords, networkName: NetworkManager.shared.selectedNetwork.name) - walletPublicKeyHex = tariWallet?.publicKey.0?.hex.0 - walletStateSubject.send(.started) - } catch let error as WalletError { - walletStateSubject.send(.startFailed(error: error)) - return - } catch { - walletStateSubject.send(.startFailed(error: .unknown)) - return - } - - try? setupBasenode() - startListeningToBaseNodeSync() - backgroundStorageCleanup() - walletStateSubject.send(.started) - } - - /// Base note basic setup. Selects previously used base node or use random node if there wasn't any node selected before. - func setupBasenode() throws { - try update(baseNode: NetworkManager.shared.selectedNetwork.selectedBaseNode, syncAfterSetting: false) - } - - /// Selects new base node peer for Tari Wallet. - /// - Parameters: - /// - baseNode: Selected base node. - /// - syncAfterSetting: Boolean. If it `true` then the wallet will try to sync with newly selected base node. - func update(baseNode: BaseNode, syncAfterSetting: Bool) throws { - - NetworkManager.shared.selectedNetwork.selectedBaseNode = baseNode - - try tariWallet?.add(baseNode: baseNode) - guard syncAfterSetting else { return } - try? tariWallet?.syncBaseNode() - } - - func createNewWallet(seedWords: SeedWords?) throws { - try FileManager.default.createDirectory( - at: connectedDatabaseDirectory, - withIntermediateDirectories: true, - attributes: nil - ) - - // start listening to wallet events first - var cancelable: AnyCancellable? - - cancelable = walletStatePublisher - .receive(on: RunLoop.main) - .sink { [weak self] walletState in - switch walletState { - case .started: - try? self?.setCurrentNetworkKeyValue() - cancelable?.cancel() - case .startFailed: - cancelable?.cancel() - case .starting, .notReady: - break - } - } - - cancelable? - .store(in: &cancelables) - - startWallet(seedWords: seedWords) - } - - func setCurrentNetworkKeyValue() throws { - _ = try tariWallet?.setKeyValue( - key: KeyValueStorageKeys.network.rawValue, - value: NetworkManager.shared.selectedNetwork.name - ) - } - - func stopWallet() { - walletStateSubject.send(.notReady) - tariWallet = nil - baseNodeConnectionStatus = .offline - TariEventBus.unregister(self) - } - - func deleteWallet() { - stopWallet() - do { - // delete database files - try FileManager.default.removeItem(at: connectedDatabaseDirectory) - // delete cached value - walletPublicKeyHex = nil - // delete log files - for logFile in allLogFiles { - try FileManager.default.removeItem(at: logFile) - } - // remove all user defaults - UserDefaults.standard.removeAll() - NetworkManager.shared.removeSelectedNetworkSettings() - } catch { - fatalError() - } - } -} - -extension TariLib: OnionConnectorDelegate { - - func onTorConnProgress(_ progress: Int) { - TariEventBus.postToMainThread(.torConnectionProgress, sender: progress) - } - - func onTorPortsOpened() { - self.torPortsOpened = true - TariEventBus.postToMainThread(.torPortsOpened, sender: nil) - } - - func onTorConnDifficulties(error: OnionError) { - TariLogger.error("Tor connection failed to complete", error: error) - TariEventBus.postToMainThread(.torConnectionFailed, sender: error) - - // might as well keep trying - if error == .invalidBridges { - OnionConnector.shared.restoreBridgeConfiguration() - } - self.startTor() - } - - func onTorConnFinished(_ configuration: BridgesConfuguration) { - TariEventBus.postToMainThread(.torConnectionProgress, sender: Int(100)) - TariEventBus.postToMainThread(.torConnected, sender: nil) - } -} diff --git a/MobileWallet/TariLib/Tor/OnionConnector.swift b/MobileWallet/TariLib/Tor/OnionConnector.swift deleted file mode 100644 index 04f1026c..00000000 --- a/MobileWallet/TariLib/Tor/OnionConnector.swift +++ /dev/null @@ -1,148 +0,0 @@ -// OnionConnecter.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/03/02 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -protocol OnionConnectorDelegate: OnionManagerDelegate { - func onTorConnDifficulties() -} -extension OnionConnectorDelegate { - func onTorConnProgress(_ progress: Int) { } - func onTorConnFinished(_ configuration: BridgesConfuguration) { } - func onTorConnDifficulties() { } - func onTorConnDifficulties(error: OnionError) { } - func onTorPortsOpened() { } -} - -public final class OnionConnector { - public static let shared = OnionConnector() - private var torObservers = NSPointerArray.weakObjects() - - var connectionState: OnionManager.TorState { - return OnionManager.shared.state - } - - var bridgesConfiguration: BridgesConfuguration { - get { - OnionSettings.currentlyUsedBridgesConfiguration - } - - set { - OnionSettings.backupBridgesConfiguration = bridgesConfiguration - OnionManager.shared.setBridgeConfiguration(bridgesType: newValue.bridgesType, customBridges: newValue.customBridges) - OnionManager.shared.stopTor { - OnionManager.shared.startTor(delegate: self) - } - } - } - - private init() {} - - public func start() { - OnionManager.shared.startTor(delegate: self) - } - - public func restoreBridgeConfiguration() { - bridgesConfiguration = OnionSettings.backupBridgesConfiguration - } - - public func stop() { - OnionManager.shared.stopTor() - } - - func addObserver(_ observer: OnionConnectorDelegate) { - torObservers.addObject(observer) - } - - func removeObserver(_ observer: OnionConnectorDelegate) { - torObservers.remove(observer) - } -} - -extension OnionConnector: OnionManagerDelegate { - - func onTorConnProgress(_ progress: Int) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.torObservers.allObjects.forEach { - if let object = $0 as? OnionConnectorDelegate { - object.onTorConnProgress(progress) - } - } - } - } - - func onTorConnFinished(_ configuration: BridgesConfuguration) { - OnionSettings.backupBridgesConfiguration = configuration - OnionSettings.currentlyUsedBridgesConfiguration = configuration - - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.torObservers.allObjects.forEach { - if let object = $0 as? OnionConnectorDelegate { - object.onTorConnFinished(configuration) - } - } - } - } - - func onTorConnDifficulties(error: OnionError) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.torObservers.allObjects.forEach { - if let observer = $0 as? OnionConnectorDelegate { - observer.onTorConnDifficulties(error: error) - observer.onTorConnDifficulties() - } - } - } - } - - func onTorPortsOpened() { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.torObservers.allObjects.forEach { - if let observer = $0 as? OnionConnectorDelegate { - observer.onTorPortsOpened() - } - } - } - } -} diff --git a/MobileWallet/TariLib/Tor/OnionManager.swift b/MobileWallet/TariLib/Tor/OnionManager.swift deleted file mode 100644 index 9d541d8f..00000000 --- a/MobileWallet/TariLib/Tor/OnionManager.swift +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Onion Browser - * Copyright (c) 2012-2018, Tigas Ventures, LLC (Mike Tigas) - * - * This file is part of Onion Browser. See LICENSE file for redistribution terms. - */ -// swiftlint:disable all - -import Foundation -import Reachability -import Tor -import IPtProxy - -enum OnionError: Error { - case connectionError - case invalidBridges - case missingCookieFile -} - -extension OnionError: LocalizedError { - public var errorDescription: String? { - switch self { - case .missingCookieFile: - return localized("Onion_Error.error.missing_cookie_file") - case .invalidBridges: - return localized("Onion_Error.error.invalid_bridges") - case .connectionError: - return localized("Onion_Error.error.connectionError") - } - } - - public var failureReason: String? { - switch self { - case .connectionError, .invalidBridges, .missingCookieFile: - return localized("Onion_Error.error.title.onionError") - } - } -} - - -protocol OnionManagerDelegate: class { - - func onTorConnProgress(_ progress: Int) - - func onTorConnFinished(_ configuration: BridgesConfuguration) - - func onTorConnDifficulties(error: OnionError) - - func onTorPortsOpened() -} - -class OnionManager: NSObject { - enum TorState: Int { - case none - case started - case connected - case stopped - } - - static let shared = OnionManager() - private var reachability: Reachability? - - // Show Tor log in iOS' app log. - private static let TOR_LOGGING = false - static let CONTROL_ADDRESS = "127.0.0.1" - static let CONTROL_PORT: UInt16 = 39069 - - static func getCookie() throws -> Data { - if let cookieURL = OnionManager.torBaseConf().dataDirectory?.appendingPathComponent("control_auth_cookie") { - let cookie = try Data(contentsOf: cookieURL) - TariLogger.tor("Using cookie for control auth") - return cookie - } else { - throw OnionError.missingCookieFile - } - } - - private static func torBaseConf() -> TorConfiguration { - // Store data in /Library/Caches/tor (Library/Caches/ is for things that can persist between - // launches -- which we'd like so we keep descriptors & etc -- but don't need to be backed up because - // they can be regenerated by the app) - let dataDir = TariSettings.storageDirectory.appendingPathComponent("tor", isDirectory: true) - - TariLogger.tor("dataDir=\(dataDir)") - - // Create tor data directory if it does not yet exist - do { - if !FileManager.default.fileExists(atPath: dataDir.path) { - try FileManager.default.createDirectory( - atPath: dataDir.path, - withIntermediateDirectories: true, - attributes: nil - ) - } - } catch let error as NSError { - TariLogger.tor("Failed to create tor directory", error: error) - } - // Create tor v3 auth directory if it does not yet exist - let authDir = URL(fileURLWithPath: dataDir.path, isDirectory: true).appendingPathComponent("auth", isDirectory: true) - do { - if !FileManager.default.fileExists(atPath: authDir.path) { - try FileManager.default.createDirectory( - atPath: authDir.path, - withIntermediateDirectories: true, - attributes: nil - ) - } - } catch let error as NSError { - TariLogger.tor("Failed to create tor auth directory", error: error) - } - - // Configure tor and return the configuration object - let configuration = TorConfiguration() - configuration.cookieAuthentication = true - configuration.dataDirectory = dataDir - - #if DEBUG - let log_loc = "notice stdout" - #else - let log_loc = "notice file /dev/null" - #endif - - configuration.arguments = [ - "--allow-missing-torrc", - "--ignore-missing-torrc", - "--clientonly", "1", - "--AvoidDiskWrites", "1", - "--socksport", "39059", - "--controlport", "\(OnionManager.CONTROL_ADDRESS):\(OnionManager.CONTROL_PORT)", - "--log", log_loc, - "--clientuseipv6", "1", - "--ClientTransportPlugin", "obfs4 socks5 127.0.0.1:47351", - "--ClientTransportPlugin", "meek_lite socks5 127.0.0.1:47352", - "--ClientOnionAuthDir", authDir.path - ] - return configuration - } - - @Published private(set) var state: TorState = .none - - private var torController: TorController? - private var torThread: TorThread? - private var initRetry: DispatchWorkItem? - private var bridgesType = OnionSettings.currentlyUsedBridgesConfiguration.bridgesType - private var customBridges = OnionSettings.currentlyUsedBridgesConfiguration.customBridges - private var needsReconfiguration = false - - private var cookie: Data? { - if let cookieUrl = OnionManager.torBaseConf().dataDirectory?.appendingPathComponent("control_auth_cookie") { - return try? Data(contentsOf: cookieUrl) - } - return nil - } - - - - func torReconnect() { - guard self.torThread != nil else { - TariLogger.tor("No tor thread, aborting reconnect") - return - } - - TariLogger.tor("Tor reconnecting...") - - torController?.resetConnection({ (complete) in - TariLogger.tor("Tor reconnected") - }) - } - - /** - Get all fully built circuits and detailed info about their nodes. - - - parameter callback: Called, when all info is available. - - parameter circuits: A list of circuits and the nodes they consist of. - */ - func getCircuits(_ callback: @escaping ((_ circuits: [TorCircuit]) -> Void)) { - torController?.getCircuits(callback) - } - - func closeCircuits(_ circuits: [TorCircuit], _ callback: @escaping ((_ success: Bool) -> Void)) { - torController?.close(circuits, completion: callback) - } - - func startIObfs4Proxy() { - IPtProxyStartObfs4Proxy() - } - - func startTor(delegate: OnionManagerDelegate?) { - // Avoid a retain cycle. Only use the weakDelegate in closures! - weak var weakDelegate = delegate - - cancelInitRetry() - state = .started - - if (self.torController == nil) { - self.torController = TorController(socketHost: OnionManager.CONTROL_ADDRESS, port: OnionManager.CONTROL_PORT) - } - - do { - try startObserveReachability() - TariLogger.tor("Listening for reachability changes to reconnect tor") - } catch { - TariLogger.tor("Failed to init Reachability", error: error) - } - - - if torThread?.isCancelled ?? true { - torThread = nil - - let torConf = OnionManager.torBaseConf() - - var args = torConf.arguments! - - args += getBridgesAsArgs() - - // configure ipv4/ipv6 - // Use Ipv6Tester. If we _think_ we're IPv6-only, tell Tor to prefer IPv6 ports. - // (Tor doesn't always guess this properly due to some internal IPv4 addresses being used, - // so "auto" sometimes fails to bootstrap.) - TariLogger.tor("ipv6_status: \(Ipv6Tester.ipv6_status())") - if (Ipv6Tester.ipv6_status() == .torIpv6ConnOnly) { - args += ["--ClientPreferIPv6ORPort", "1"] - - if bridgesType != .none { - // Bridges on, leave IPv4 on. - // User's bridge config contains all the IPs (v4 or v6) - // that we connect to, so we let _that_ setting override our - // "IPv6 only" self-test. - args += ["--ClientUseIPv4", "1"] - } - else { - // Otherwise, for IPv6-only no-bridge state, disable IPv4 - // connections from here to entry/guard nodes. - // (i.e. all outbound connections are ipv6 only.) - args += ["--ClientUseIPv4", "0"] - } - } - else { - args += [ - "--ClientPreferIPv6ORPort", "auto", - "--ClientUseIPv4", "1", - ] - } - - #if DEBUG - TariLogger.tor("arguments=\(String(describing: args))") - #endif - - torConf.arguments = args - torThread = TorThread(configuration: torConf) - needsReconfiguration = false - - torThread?.start() - startIObfs4Proxy() - TariLogger.tor("Starting Tor") - } - else { - if needsReconfiguration { - let conf = getBridgesAsConf() - - torController?.resetConf(forKey: "Bridge") - - if conf.count > 0 { - // Bridges need to be set *before* "UseBridges"="1"! - torController?.setConfs(conf) - torController?.setConfForKey("UseBridges", withValue: "1") - } - else { - torController?.setConfForKey("UseBridges", withValue: "0") - } - } - } - - // Wait long enough for Tor itself to have started. It's OK to wait for this - // because Tor is already trying to connect; this is just the part that polls for - // progress. - DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { - if OnionManager.TOR_LOGGING { - // Show Tor log in iOS' app log. - TORInstallTorLoggingCallback { severity, msg in - let s: String - - switch severity { - case .debug: - s = "debug" - - case .error: - s = "error" - - case .fault: - s = "fault" - - case .info: - s = "info" - - default: - s = "default" - } - - TariLogger.tor("[Tor libevent \(s)] \(String(cString: msg))") - } - TORInstallEventLoggingCallback { severity, msg in - let s: String - - switch severity { - case .debug: - // Ignore libevent debug messages. Just too many of typically no importance. - return - - case .error: - s = "error" - - case .fault: - s = "fault" - - case .info: - s = "info" - - default: - s = "default" - } - TariLogger.tor("[Tor libevent \(s)] \(String(cString: msg).trimmingCharacters(in: .whitespacesAndNewlines))") - } - } - - if !(self.torController?.isConnected ?? false) { - do { - try self.torController?.connect() - } catch { - TariLogger.tor("Tor controller connection", error: error) - } - } - - guard let cookie = self.cookie else { - TariLogger.tor("Could not connect to Tor - invalid bridges!") - delegate?.onTorConnDifficulties(error: OnionError.invalidBridges) - return - } - - #if DEBUG - TariLogger.tor("cookie= \(cookie.base64EncodedString())") - #endif - - self.torController?.authenticate(with: cookie, completion: { success, error in - if success { - delegate?.onTorPortsOpened() - var completeObs: Any? - completeObs = self.torController?.addObserver(forCircuitEstablished: { established in - if established { - self.state = .connected - self.torController?.removeObserver(completeObs) - self.cancelInitRetry() - #if DEBUG - TariLogger.tor("Connection established!") - #endif - let bridgeConfiguration = BridgesConfuguration(bridges: self.bridgesType, customBridges: self.customBridges) - weakDelegate?.onTorConnFinished(bridgeConfiguration) - } - }) // torController.addObserver - - var progressObs: Any? - progressObs = self.torController?.addObserver(forStatusEvents: { - (type: String, severity: String, action: String, arguments: [String : String]?) -> Bool in - - if type == "STATUS_CLIENT" && action == "BOOTSTRAP" { - let progress = Int(arguments!["PROGRESS"]!)! - #if DEBUG - TariLogger.tor("progress=\(progress)") - #endif - - weakDelegate?.onTorConnProgress(progress) - - if progress >= 100 { - self.torController?.removeObserver(progressObs) - } - - return true - } - - return false - }) - } else { - TariLogger.tor("Didn't connect to control port.", error: error) - } - }) // controller authenticate - }) //delay - - initRetry = DispatchWorkItem { - #if DEBUG - TariLogger.tor("Triggering Tor connection retry.") - #endif - - self.torController?.setConfForKey("DisableNetwork", withValue: "1") - self.torController?.setConfForKey("DisableNetwork", withValue: "0") - - // Hint user that they might need to use a bridge. - delegate?.onTorConnDifficulties(error: OnionError.connectionError) - } - - // On first load: If Tor hasn't finished bootstrap in 30 seconds, - // HUP tor once in case we have partially bootstrapped but got stuck. - DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: initRetry!) - } - - /** - Experimental Tor shutdown. - */ - func stopTor(completion:(() -> Void)? = nil) { // completion only in foreground - TariLogger.tor("Stopping tor") - - // Under the hood, TORController will SIGNAL SHUTDOWN and set it's channel to nil, so - // we actually rely on that to stop Tor and reset the state of torController. (we can - // SIGNAL SHUTDOWN here, but we can't reset the torController "isConnected" state.) - torController?.disconnect() - torController = nil - - // More cleanup - torThread?.cancel() - - Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] (timer) in - if (self?.torThread == nil || ((self?.torThread?.isFinished) == true && self?.torThread?.isExecuting == false)) { - self?.torThread = nil - timer.invalidate() - completion?() - } - } - - state = .stopped - } - - /** - Set bridges configuration and evaluate, if the new configuration is actually different - then the old one. - - - parameter bridgesType: the selected ID as defined in OBSettingsConstants. - - parameter customBridges: a list of custom bridges the user configured. - */ - func setBridgeConfiguration(bridgesType: OnionSettings.BridgesType, customBridges: [String]?) { - needsReconfiguration = bridgesType != self.bridgesType - - if !needsReconfiguration { - if let oldVal = self.customBridges, let newVal = customBridges { - needsReconfiguration = oldVal != newVal - } - else{ - needsReconfiguration = (self.customBridges == nil && customBridges != nil) || - (self.customBridges != nil && customBridges == nil) - } - } - - self.bridgesType = bridgesType - self.customBridges = customBridges - } -} - -// MARK: Private Methods - - -extension OnionManager { - /** - - returns: The list of bridges which is currently configured to be valid. - */ - private func getBridges() -> [String] { - #if DEBUG - TariLogger.tor("bridgesId=\(bridgesType)") - #endif - - switch bridgesType { - case .custom: - return customBridges ?? [] - - default: - return [] - } - } - - /** - - returns: The list of bridges which is currently configured to be valid *as argument list* to be used on Tor startup. - */ - private func getBridgesAsArgs() -> [String] { - var args = [String]() - - for bridge in getBridges() { - args += ["--Bridge", bridge] - } - - if args.count > 0 { - args.append(contentsOf: ["--UseBridges", "1"]) - } - - return args - } - - /** - Each bridge line needs to be wrapped in double-quotes ("). - - - returns: The list of bridges which is currently configured to be valid *as configuration list* to be used with `TORController#setConfs`. - */ - private func getBridgesAsConf() -> [[String: String]] { - return getBridges().map { ["key": "Bridge", "value": "\"\($0)\""] } - } - - /** - Cancel the connection retry and fail guard. - */ - private func cancelInitRetry() { - initRetry?.cancel() - initRetry = nil - } -} - -// MARK: Reachability - -extension OnionManager { - @objc private func networkChange() { - TariLogger.tor("ipv6_status: \(Ipv6Tester.ipv6_status())") - var confs:[Dictionary] = [] - - if (Ipv6Tester.ipv6_status() == .torIpv6ConnOnly) { - // We think we're on a IPv6-only DNS64/NAT64 network. - confs.append(["key": "ClientPreferIPv6ORPort", "value": "1"]) - - if (self.bridgesType != .none) { - // Bridges on, leave IPv4 on. - // User's bridge config contains all the IPs (v4 or v6) - // that we connect to, so we let _that_ setting override our - // "IPv6 only" self-test. - confs.append(["key": "ClientUseIPv4", "value": "1"]) - } - else { - // Otherwise, for IPv6-only no-bridge state, disable IPv4 - // connections from here to entry/guard nodes. - //(i.e. all outbound connections are IPv6 only.) - confs.append(["key": "ClientUseIPv4", "value": "0"]) - } - } else { - // default mode - confs.append(["key": "ClientPreferIPv6DirPort", "value": "auto"]) - confs.append(["key": "ClientPreferIPv6ORPort", "value": "auto"]) - confs.append(["key": "ClientUseIPv4", "value": "1"]) - } - - torController?.setConfs(confs, completion: { _, _ in - self.torReconnect() - }) - } - - private func startObserveReachability() throws { - stopObserveReachability() - reachability = try Reachability() - NotificationCenter.default.addObserver(self, selector: #selector(networkChange), - name: .reachabilityChanged, object: nil) - try reachability?.startNotifier() - } - - private func stopObserveReachability() { - reachability?.stopNotifier() - NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: nil) - } -} diff --git a/MobileWallet/TariLib/Wrappers/ByteVector.swift b/MobileWallet/TariLib/Wrappers/ByteVector.swift deleted file mode 100644 index 9eed6e86..00000000 --- a/MobileWallet/TariLib/Wrappers/ByteVector.swift +++ /dev/null @@ -1,137 +0,0 @@ -// ByteVector.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/14 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum ByteVectorError: Error { - case generic(_ errorCode: Int32) -} - -class ByteVector { - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var count: (UInt32, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - byte_vector_get_length(ptr, error) - }) - return (result, errorCode != 0 ? ByteVectorError.generic(errorCode) : nil ) - } - - var bytes: ([UInt8], Error?) { - var byteArray: [UInt8] = [UInt8]() - - let (byteArrayLength, error) = self.count - - if error != nil { - return ([], error) - } - - for n in 0...byteArrayLength - 1 { - do { - let byte = try self.at(position: n) - byteArray.append(byte) - } catch { - return ([], error) - } - } - - return (byteArray, nil) - } - - var hexString: (String, Error?) { - let (byteArray, error) = self.bytes - - if error != nil { - return ("", error) - } - - let data = Data(byteArray) - - return (data.map {String(format: "%02hhx", $0)}.joined(), nil) - } - - var utf8: (String?, Error?) { - let (byteArray, error) = self.bytes - - if error != nil { - return ("", error) - } - - return (String.init(data: Data(byteArray), encoding: .utf8), nil) - } - - init(byteArray: [UInt8]) throws { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - byte_vector_create(byteArray, UInt32(byteArray.count), error) - }) - guard errorCode == 0 else { - throw ByteVectorError.generic(errorCode) - } - - ptr = result! - } - - init (pointer: OpaquePointer) { - ptr = pointer - } - - func at(position: UInt32) throws -> (UInt8) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - byte_vector_get_at(ptr, position, error) - }) - - guard errorCode == 0 else { - throw ByteVectorError.generic(errorCode) - } - - return result - } - - deinit { - byte_vector_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Contacts.swift b/MobileWallet/TariLib/Wrappers/Contacts.swift deleted file mode 100644 index 42a504c0..00000000 --- a/MobileWallet/TariLib/Wrappers/Contacts.swift +++ /dev/null @@ -1,140 +0,0 @@ -// Contacts.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/16 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum ContactsError: Error { - case generic(_ errorCode: Int32) - case contactNotFound -} - -class Contacts { - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var count: (UInt32, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - contacts_get_length(ptr, error)}) - return (result, errorCode != 0 ? ContactsError.generic(errorCode) : nil) - } - - var list: ([Contact], Error?) { - let (count, countError) = self.count - guard countError == nil else { - return ([], countError) - } - - var list: [Contact] = [] - - if count > 0 { - for n in 0...count - 1 { - do { - list.append(try self.at(position: n)) - } catch { - return ([], error) - } - } - } - - return (list, nil) - } - - init(contactsPointer: OpaquePointer) { - ptr = contactsPointer - } - - func at(position: UInt32) throws -> Contact { - var errorCode: Int32 = -1 - let contactPointer = withUnsafeMutablePointer(to: &errorCode, { error in - contacts_get_at(ptr, position, error)}) - guard errorCode == 0 else { - throw ContactsError.generic(errorCode) - } - if contactPointer == nil { - throw ContactsError.contactNotFound - } - - return Contact(contactPointer: contactPointer!) - } - - // TODO this might be more better to be searched by in the lib instead of iterating though them here - func find(publicKey: PublicKey) throws -> Contact { - let (searchHex, seachHexError) = publicKey.hex - guard seachHexError == nil else { - throw seachHexError! - } - - let (count, countError) = self.count - guard countError == nil else { - throw countError! - } - - if count < 1 { - throw ContactsError.contactNotFound - } - - for n in 0...count - 1 { - let contact = try self.at(position: n) - let (contactPubKey, contactPubKeyError) = contact.publicKey - guard contactPubKeyError == nil else { - throw contactPubKeyError! - } - - let (contactHex, contactHexError) = contactPubKey!.hex - guard contactHexError == nil else { - throw contactHexError! - } - - if searchHex == contactHex { - return contact - } - } - - throw ContactsError.contactNotFound - } - - deinit { - contacts_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/PrivateKey.swift b/MobileWallet/TariLib/Wrappers/PrivateKey.swift deleted file mode 100644 index 6dcc7294..00000000 --- a/MobileWallet/TariLib/Wrappers/PrivateKey.swift +++ /dev/null @@ -1,111 +0,0 @@ -// PrivateKey.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/15 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PrivateKeyError: Error { - case generic(_ errorCode: Int32) - case invalidHex -} - -class PrivateKey { - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var bytes: (ByteVector?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - ByteVector(pointer: private_key_get_bytes(ptr, error)) - }) - guard errorCode == 0 else { - return (nil, PrivateKeyError.generic(errorCode)) - } - - return (result, nil) - } - - var hex: (String, Error?) { - let (bytes, bytesError) = self.bytes - if bytesError != nil { - return ("", bytesError) - } - - return bytes!.hexString - } - - init(byteVector: ByteVector) throws { - var errorCode: Int32 = -1 - self.ptr = withUnsafeMutablePointer(to: &errorCode, { error in - private_key_create(byteVector.pointer, error) - }) - guard errorCode == 0 else { - throw PrivateKeyError.generic(errorCode) - } - } - - init(hex: String) throws { - let chars = CharacterSet(charactersIn: "0123456789abcdef") - guard hex.count == 64 && hex.rangeOfCharacter(from: chars) != nil else { - throw PrivateKeyError.invalidHex - } - - var errorCode: Int32 = -1 - let result = hex.withCString({ chars in - withUnsafeMutablePointer(to: &errorCode, { error in - private_key_from_hex(chars, error) - }) - }) - guard errorCode == 0 else { - throw PrivateKeyError.generic(errorCode) - } - ptr = result! - } - - init() { - ptr = private_key_generate() - } - - deinit { - private_key_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/PublicKey.swift b/MobileWallet/TariLib/Wrappers/PublicKey.swift deleted file mode 100644 index 02438b93..00000000 --- a/MobileWallet/TariLib/Wrappers/PublicKey.swift +++ /dev/null @@ -1,320 +0,0 @@ -// PublicKey.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/16 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PublicKeyError: Error { - case generic(_ errorCode: Int32) - case invalidEmojis - case invalidHex - case invalidDeepLink - case invalidDeepLinkNetwork // When a deep link is valid but for wring network - case invalidDeepLinkType // If it doesn't contain "/eid/" or "/pubkey/" - case cantDerivePublicKeyFromString - case invalidEmojiSet -} - -class PublicKey { - private var ptr: OpaquePointer - private var cachedEmojiId: String? - private static let emojiCount = 33 // Used for some pre validation before hitting the FFI - - var pointer: OpaquePointer { - return ptr - } - - var bytes: (ByteVector?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - ByteVector(pointer: public_key_get_bytes(ptr, error)) - }) - guard errorCode == 0 else { - return (nil, PublicKeyError.generic(errorCode)) - } - - return (result, nil) - } - - var hex: (String, Error?) { - let (bytes, bytesError) = self.bytes - if bytesError != nil { - return ("", bytesError) - } - - return bytes!.hexString - } - - var emojis: (String, Error?) { - var errorCode: Int32 = -1 - let emojiPtr = withUnsafeMutablePointer(to: &errorCode, { error in - public_key_to_emoji_id(ptr, error) - }) - let result = String(cString: emojiPtr!) - - let mutable = UnsafeMutablePointer(mutating: emojiPtr!) - string_destroy(mutable) - - return (result, errorCode != 0 ? PublicKeyError.generic(errorCode) : nil) - } - - var emojiDeeplink: (String, Error?) { - let (emojisPubkey, emojisError) = emojis - guard emojisError == nil else { - return ("", emojisError) - } - - return ("\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/eid/\(emojisPubkey)", nil) - } - - var hexDeeplink: (String, Error?) { - let (hexPubkey, hexError) = hex - guard hexError == nil else { - return ("", hexError) - } - - return ("\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/pubkey/\(hexPubkey)", nil) - } - - // TODO setup attributed string version with dots in the middle for shortened version in Common dir. - //https://stackoverflow.com/questions/19318421/how-to-embed-small-icon-in-uilabel - - init(privateKey: PrivateKey) throws { - var errorCode: Int32 = -1 - ptr = withUnsafeMutablePointer(to: &errorCode, { error in - public_key_from_private_key(privateKey.pointer, error) - }) - guard errorCode == 0 else { - throw PublicKeyError.generic(errorCode) - } - } - - init(emojis: String) throws { - let cleanEmojis = emojis - .replacingOccurrences(of: "|", with: "") - .replacingOccurrences(of: "`", with: "") - .replacingOccurrences(of: " ", with: "") - - if cleanEmojis.count < PublicKey.emojiCount { - throw PublicKeyError.invalidEmojis - } - - let count = cleanEmojis.utf8.count + 1 - let emojiPtr = UnsafeMutablePointer.allocate(capacity: count) - cleanEmojis.withCString { (baseAddress) in - emojiPtr.initialize(from: baseAddress, count: count) - } - - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - emoji_id_to_public_key(emojiPtr, error)}) - - emojiPtr.deinitialize(count: count) - - guard errorCode == 0 else { - throw PublicKeyError.generic(errorCode) - } - - ptr = result! - } - - init(hex: String) throws { - let chars = CharacterSet(charactersIn: "0123456789abcdef") - guard hex.count == 64 && hex.rangeOfCharacter(from: chars) != nil else { - throw PublicKeyError.invalidHex - } - var errorCode: Int32 = -1 - let result = hex.withCString({ chars in - withUnsafeMutablePointer(to: &errorCode, { error in - public_key_from_hex(chars, error)}) - }) - guard errorCode == 0 else { - throw PublicKeyError.generic(errorCode) - } - ptr = result! - } - - // Accepts deep links using either emoji ID or hex. i.e: - // tari://rincewind/eid/๐Ÿ’๐Ÿ‘๐Ÿ”๐Ÿ”งโŒ๐Ÿ‘‚๐Ÿฆ’๐Ÿ’‡๐Ÿ”‹๐Ÿ’ฅ๐Ÿท๐Ÿบ๐Ÿ‘”๐Ÿ˜ท๐Ÿถ๐Ÿงข๐Ÿคฉ๐Ÿ’ฅ๐ŸŽพ๐ŸŽฒ๐Ÿ€๐Ÿค ๐Ÿ’ช๐Ÿ‘ฎ๐Ÿคฏ๐ŸŽ๐Ÿ’‰๐ŸŒž๐Ÿ‰๐Ÿคท๐Ÿฆ - // tari://rincewind/pubkey/70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a?amount=2.3note=hi%20there - convenience init(deeplink: String) throws { - guard deeplink.hasPrefix("\(TariSettings.shared.deeplinkURI)://") else { - throw PublicKeyError.invalidDeepLink - } - - let deeplinkPrefix = "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)" - - // Link is for a different network - guard deeplink.hasPrefix(deeplinkPrefix) else { - throw PublicKeyError.invalidDeepLinkNetwork - } - - guard let strippedParamsLink = PublicKey.removeDeepURLParams(deeplink).removingPercentEncoding else { - throw PublicKeyError.invalidDeepLink - } - - if strippedParamsLink.hasPrefix("\(deeplinkPrefix)/eid/") { - let emojis = strippedParamsLink.replacingOccurrences(of: "\(deeplinkPrefix)/eid/", with: "") - try self.init(emojis: emojis) - return - } else if strippedParamsLink.hasPrefix("\(deeplinkPrefix)/pubkey/") { - let hex = strippedParamsLink.replacingOccurrences(of: "\(deeplinkPrefix)/pubkey/", with: "") - try self.init(hex: hex) - return - } - - throw PublicKeyError.invalidDeepLinkType - } - - // Attempts to derive a pubkey from a emoji deeplink, or hex string (In order of most likely) - convenience init(any: String) throws { - do { - try self.init(emojis: any) - return - } catch {} - - do { - try self.init(deeplink: any) - return - } catch {} - - do { - try self.init(hex: any) - return - } catch {} - - let filteredEmojis = PublicKey.filterEmojis(any) - // Attempt to strip out a valid emoji ID from the string and init again - if filteredEmojis.count >= PublicKey.emojiCount { - do { - try self.init(emojis: filteredEmojis) - return - } catch {} - } - - // User might have an emoji ID from an outdated set - if PublicKey.isOldEmojiSet(any) { - throw PublicKeyError.invalidEmojiSet - } - - throw PublicKeyError.cantDerivePublicKeyFromString - } - - init(pointer: OpaquePointer) { - ptr = pointer - } - - private static func removeDeepURLParams(_ link: String) -> String { - guard let startIndex = link.lastIndex(of: "?") else { - return link - } - - let endIndex = link.index(link.endIndex, offsetBy: -1) - - var strippedLink = link - strippedLink.removeSubrange(startIndex...endIndex) - - return strippedLink - } - - private static func filterEmojis(_ text: String) -> String { - var emojis = "" - - let fullEmojiSet = EmojiSet().list.0 - - for scalar in text.unicodeScalars { - if fullEmojiSet.contains(String(scalar)) { - emojis.append(Character(scalar)) - } - } - - return emojis - } - - private static func isOldEmojiSet(_ text: String) -> Bool { - let oldEmojiSet = [ - "๐Ÿ˜€", "๐Ÿ˜‚", "๐Ÿคฃ", "๐Ÿ˜‰", "๐Ÿ˜Š", "๐Ÿ˜Ž", "๐Ÿ˜", "๐Ÿ˜˜", "๐Ÿค—", "๐Ÿคฉ", "๐Ÿค”", "๐Ÿ™„", "๐Ÿ˜ฎ", "๐Ÿค", "๐Ÿ˜ด", "๐Ÿ˜›", "๐Ÿคค", "๐Ÿ™ƒ", "๐Ÿค‘", - "๐Ÿ˜ค", "๐Ÿ˜จ", "๐Ÿคฏ", "๐Ÿ˜ฌ", "๐Ÿ˜ฑ", "๐Ÿคช", "๐Ÿ˜ต", "๐Ÿ˜ท", "๐Ÿคข", "๐Ÿคฎ", "๐Ÿค ", "๐Ÿคก", "๐Ÿคซ", "๐Ÿคญ", "๐Ÿค“", "๐Ÿ˜ˆ", "๐Ÿ‘ป", "๐Ÿ‘ฝ", "๐Ÿค–", - "๐Ÿ’ฉ", "๐Ÿ˜บ", "๐Ÿ‘ถ", "๐Ÿ‘ฉ", "๐Ÿ‘จ", "๐Ÿ‘ฎ", "๐Ÿคด", "๐Ÿ‘ธ", "๐Ÿงœ", "๐Ÿ™…", "๐Ÿ™‹", "๐Ÿคฆ", "๐Ÿคท", "๐Ÿ’‡", "๐Ÿƒ", "๐Ÿ’ƒ", "๐Ÿง—", "๐Ÿ›€", "๐Ÿ›Œ", - "๐Ÿ‘ค", "๐Ÿ„", "๐Ÿšด", "๐Ÿคน", "๐Ÿ’", "๐Ÿ‘ช", "๐Ÿ’ช", "๐Ÿ‘ˆ", "๐Ÿ‘", "โœ‹", "๐Ÿ‘Š", "๐Ÿ‘", "๐Ÿ™", "๐Ÿค", "๐Ÿ’…", "๐Ÿ‘‚", "๐Ÿ‘€", "๐Ÿง ", "๐Ÿ‘„", - "๐Ÿ’”", "๐Ÿ’–", "๐Ÿ’™", "๐Ÿ’Œ", "๐Ÿ’ค", "๐Ÿ’ฃ", "๐Ÿ’ฅ", "๐Ÿ’ฆ", "๐Ÿ’จ", "๐Ÿ’ซ", "๐Ÿ‘”", "๐Ÿ‘•", "๐Ÿ‘–", "๐Ÿงฃ", "๐Ÿงค", "๐Ÿงฆ", "๐Ÿ‘—", "๐Ÿ‘™", "๐Ÿ‘œ", - "๐ŸŽ’", "๐Ÿ‘‘", "๐Ÿงข", "๐Ÿ’", "๐Ÿ’Ž", "๐Ÿ’", "๐Ÿถ", "๐Ÿฆ", "๐Ÿด", "๐Ÿฆ„", "๐Ÿฎ", "๐Ÿท", "๐Ÿ‘", "๐Ÿซ", "๐Ÿฆ’", "๐Ÿ˜", "๐Ÿญ", "๐Ÿ‡", "๐Ÿ”", - "๐Ÿฆ†", "๐Ÿธ", "๐Ÿ", "๐Ÿณ", "๐Ÿš", "๐Ÿฆ€", "๐ŸŒ", "๐Ÿฆ‹", "๐ŸŒธ", "๐ŸŒฒ", "๐ŸŒต", "๐Ÿ‡", "๐Ÿ‰", "๐ŸŒ", "๐ŸŽ", "๐Ÿ’", "๐Ÿ“", "๐Ÿฅ‘", "๐Ÿฅ•", - "๐ŸŒฝ", "๐Ÿ„", "๐Ÿฅœ", "๐Ÿž", "๐Ÿง€", "๐Ÿ–", "๐Ÿ”", "๐ŸŸ", "๐Ÿ•", "๐Ÿฟ", "๐Ÿฆ", "๐Ÿช", "๐Ÿฐ", "๐Ÿซ", "๐Ÿฌ", "๐Ÿท", "๐Ÿบ", "๐Ÿด", "๐ŸŒ", - "๐ŸŒ‹", "๐Ÿ ", "โ›บ", "๐ŸŽก", "๐ŸŽข", "๐ŸŽจ", "๐Ÿš‚", "๐ŸšŒ", "๐Ÿš‘", "๐Ÿš’", "๐Ÿš”", "๐Ÿš•", "๐Ÿšœ", "๐Ÿšฒ", "โ›ฝ", "๐Ÿšฆ", "๐Ÿšง", "โ›ต", "๐Ÿšข", - "๐Ÿ›ซ", "๐Ÿ’บ", "๐Ÿš", "๐Ÿš€", "๐Ÿ›ธ", "๐Ÿšช", "๐Ÿšฝ", "๐Ÿšฟ", "โŒ›", "โฐ", "๐Ÿ•™", "๐ŸŒ›", "๐ŸŒž", "โ›…", "๐ŸŒ€", "๐ŸŒˆ", "๐ŸŒ‚", "๐Ÿ”ฅ", "โœจ", - "๐ŸŽˆ", "๐ŸŽ‰", "๐ŸŽ€", "๐ŸŽ", "๐Ÿ†", "๐Ÿ…", "โšฝ", "๐Ÿ€", "๐Ÿˆ", "๐ŸŽพ", "๐ŸฅŠ", "๐ŸŽฏ", "โ›ณ", "๐ŸŽฃ", "๐ŸŽฎ", "๐ŸŽฒ", "๐Ÿ”ˆ", "๐Ÿ””", "๐ŸŽถ", - "๐ŸŽค", "๐ŸŽง", "๐Ÿ“ป", "๐ŸŽธ", "๐ŸŽน", "๐ŸŽบ", "๐ŸŽป", "๐Ÿฅ", "๐Ÿ“ฑ", "๐Ÿ”‹", "๐Ÿ’ป", "๐Ÿ“ท", "๐Ÿ”", "๐Ÿ”ญ", "๐Ÿ“ก", "๐Ÿ’ก", "๐Ÿ”ฆ", "๐Ÿ“–", "๐Ÿ“š", - "๐Ÿ“", "๐Ÿ“…", "๐Ÿ“Œ", "๐Ÿ“Ž", "๐Ÿ”’", "๐Ÿ”‘", "๐Ÿ”จ", "๐Ÿน", "๐Ÿ”ง", "๐Ÿ’‰", "๐Ÿ’Š", "๐Ÿง", "โ›”", "๐Ÿšซ", "โœ…", "โŒ", "โ“", "โ•", "๐Ÿ’ฏ", - "๐Ÿ†—", "๐Ÿ†˜", "โฌ›", "๐Ÿ”ถ", "๐Ÿ”ต", "๐Ÿ", "๐Ÿšฉ", "๐ŸŽŒ", "๐Ÿด" - ] - - var cleanEmojis = "" - - // Extract old emojis from string - for scalar in text.unicodeScalars { - if oldEmojiSet.contains(String(scalar)) { - cleanEmojis.append(Character(scalar)) - } - } - print("cleanEmojis: ", cleanEmojis.count) - - if cleanEmojis.count == PublicKey.emojiCount { - return true - } - - return false - } - - deinit { - public_key_destroy(ptr) - } -} - -extension PublicKey: Equatable { - static func == (lhs: PublicKey, rhs: PublicKey) -> Bool { - return lhs.hex.0 == rhs.hex.0 - } -} - -extension PublicKey: Hashable { - func hash(into hasher: inout Hasher) { - hasher.combine(hex.0) - } -} diff --git a/MobileWallet/TariLib/Wrappers/SeedWords.swift b/MobileWallet/TariLib/Wrappers/SeedWords.swift deleted file mode 100644 index 7c4aeb48..00000000 --- a/MobileWallet/TariLib/Wrappers/SeedWords.swift +++ /dev/null @@ -1,123 +0,0 @@ -// SeedWords.swift - -/* - Package MobileWallet - Created by David Main on 6/11/20 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -final class SeedWords { - - enum Error: Int, CoreError { - case invalidSeedWord, invalidSeedPhrase, unexpectedResult, phraseIsTooShort, phraseIsTooLong - - var code: Int { rawValue } - var domain: String { "SWE" } - } - - private enum PushWordResult: UInt8 { - case invalidSeedWord, successfulPush, seedPhraseComplete, invalidSeedPhrase - } - - // MARK: - Properties - - let pointer: OpaquePointer - - // MARK: - Initializers - - init(walletPointer: OpaquePointer) throws { - var errorCode: Int32 = -1 - self.pointer = withUnsafeMutablePointer(to: &errorCode) { wallet_get_seed_words(walletPointer, $0) } - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - } - - init(words: [String]) throws { - - self.pointer = seed_words_create() - - let lastIndex = words.count - 1 - - for word in words.enumerated() { - - var errorCode: Int32 = -1 - - let rawResult = withUnsafeMutablePointer(to: &errorCode) { - seed_words_push_word(pointer, word.element, $0) - } - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - guard let result = PushWordResult(rawValue: rawResult) else { throw Error.unexpectedResult } - - switch result { - case .invalidSeedWord: - throw Error.invalidSeedWord - case .successfulPush: - guard word.offset < lastIndex else { throw Error.phraseIsTooShort } - case .seedPhraseComplete: - guard word.offset == lastIndex else { throw Error.phraseIsTooLong } - case .invalidSeedPhrase: - throw Error.invalidSeedPhrase - } - } - } - - // MARK: - Actions - - func count() throws -> UInt32 { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { seed_words_get_length(pointer, $0) } - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - func element(at position: UInt32) throws -> String { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { seed_words_get_at(pointer, position, $0) } - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - guard let result = result, let seedWord = String(validatingUTF8: result) else { return "" } - return seedWord - } - - func allElements() throws -> [String] { - let seedWordsCount = try count() - return try (0..(mutating: resultPtr!) - string_destroy(mutable) - - return (result, errorCode != 0 ? CompletedTxError.generic(errorCode) : nil) - } - - var timestamp: (UInt64, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_get_timestamp(ptr, error)}) - return (result, errorCode != 0 ? CompletedTxError.generic(errorCode) : nil) - } - - var sourcePublicKey: (PublicKey?, Error?) { - var errorCode: Int32 = -1 - let resultPointer = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_get_source_public_key(ptr, error) - }) - guard errorCode == 0 else { - return (nil, CompletedTxError.generic(errorCode)) - } - - return (PublicKey(pointer: resultPointer!), nil) - } - - var destinationPublicKey: (PublicKey?, Error?) { - var errorCode: Int32 = -1 - let resultPointer = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_get_destination_public_key(ptr, error) - }) - guard errorCode == 0 else { - return (nil, CompletedTxError.generic(errorCode)) - } - - return (PublicKey(pointer: resultPointer!), nil) - } - - var transactionKernel: (CompletedTxKernel?, Error?) { - var errorCode: Int32 = -1 - let resultPointer = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_get_transaction_kernel(ptr, error) - }) - guard errorCode == 0 else { - return (nil, CompletedTxKernelError.generic(errorCode)) - } - - return (CompletedTxKernel(pointer: resultPointer!), nil) - } - - var status: (TxStatus, Error?) { - var errorCode: Int32 = -1 - let statusCode: Int32 = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_get_status(ptr, error)}) - guard errorCode == 0 else { - return (.unknown, CompletedTxError.generic(errorCode)) - } - - return (statusFrom(code: statusCode), nil) - } - - var direction: TxDirection { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transaction_is_outbound(ptr, error)}) - guard errorCode == 0 else { - return .none - } - - if result { - return .outbound - } - - return .inbound - } - - var contact: (Contact?, Error?) { - if cachedContact != nil { - return (cachedContact, nil) - } - - guard let wallet = TariLib.shared.tariWallet else { - return (nil, WalletErrors.walletNotInitialized) - } - - let (contacts, contactsError) = wallet.contacts - guard contactsError == nil else { - return (nil, contactsError) - } - - let (pubKey, pubKeyError) = self.direction == TxDirection.inbound ? self.sourcePublicKey : self.destinationPublicKey - guard pubKeyError == nil else { - return (nil, pubKeyError) - } - - do { - cachedContact = try contacts!.find(publicKey: pubKey!) - return (cachedContact, nil) - } catch { - return (nil, error) - } - } - - var rejectionReason: TransactionRejectionReason { - get throws { - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let result = completed_transaction_get_cancellation_reason(ptr, errorCodePointer) - - guard errorCode == 0 else { throw CompletedTxError.generic(errorCode) } - return TransactionRejectionReason(code: result) - } - } - - init(completedTxPointer: OpaquePointer, isCancelled: Bool = false) { - ptr = completedTxPointer - self.isCancelled = isCancelled - } - - deinit { - completed_transaction_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/CompletedTxs.swift b/MobileWallet/TariLib/Wrappers/Transactions/CompletedTxs.swift deleted file mode 100644 index 71be3cb9..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/CompletedTxs.swift +++ /dev/null @@ -1,117 +0,0 @@ -// CompletedTx.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/17 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum CompletedTxsErrors: Error { - case completedTxNotFound - case generic(_ errorCode: Int32) -} - -class CompletedTxs: TxsProtocol { - typealias Tx = CompletedTx - - private var ptr: OpaquePointer - private let isCancelled: Bool - - var pointer: OpaquePointer { - return ptr - } - - var count: (UInt32, Error?) { - var errorCode: Int32 = -1 - - let result = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transactions_get_length(ptr, error) - }) - - return (result, errorCode != 0 ? CompletedTxsErrors.generic(errorCode) : nil) - } - - var list: ([CompletedTx], Error?) { - let (count, countError) = self.count - guard countError == nil else { - return ([], countError) - } - - var list: [CompletedTx] = [] - - if count > 0 { - for n in 0...count - 1 { - do { - let tx = try self.at(position: n) - list.append(tx) - } catch { - return ([], error) - } - } - } - - let sortedList = list.sorted(by: { $0.date.0?.compare($1.date.0!) == .orderedDescending }) - - return (sortedList, nil) - } - - init(completedTxsPointer: OpaquePointer, isCancelled: Bool = false) { - ptr = completedTxsPointer - self.isCancelled = isCancelled - } - - func at(position: UInt32) throws -> CompletedTx { - var errorCode: Int32 = -1 - let completedTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - completed_transactions_get_at(ptr, position, error) - - }) - guard errorCode == 0 else { - throw CompletedTxsErrors.generic(errorCode) - } - - if completedTxPointer == nil { - throw CompletedTxsErrors.completedTxNotFound - } - - return CompletedTx(completedTxPointer: completedTxPointer!, isCancelled: isCancelled) - } - - deinit { - completed_transactions_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTx.swift b/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTx.swift deleted file mode 100644 index f3b0de67..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTx.swift +++ /dev/null @@ -1,160 +0,0 @@ -// PendingInboundTx.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/18 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PendingInboundTxError: Error { - case generic(_ errorCode: Int32) -} - -class PendingInboundTx: TxProtocol { - var cachedContact: Contact? - - var destinationPublicKey: (PublicKey?, Error?) - var transactionKernel: (CompletedTxKernel?, Error?) - - private var ptr: OpaquePointer - - var direction: TxDirection = .inbound - - var pointer: OpaquePointer { - return ptr - } - - var id: (UInt64, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_transaction_id(ptr, error) - }) - return (result, errorCode != 0 ? PendingInboundTxError.generic(errorCode) : nil) - } - - var microTari: (MicroTari?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_amount(ptr, error) }) - - guard errorCode == 0 else { - return (nil, CompletedTxError.generic(errorCode)) - } - - return (MicroTari(result), nil) - } - - var message: (String, Error?) { - var errorCode: Int32 = -1 - let resultPtr = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_message(ptr, error) - }) - let result = String(cString: resultPtr!) - - let mutable = UnsafeMutablePointer(mutating: resultPtr!) - string_destroy(mutable) - - return (result, errorCode != 0 ? PendingInboundTxError.generic(errorCode) : nil) - } - - var timestamp: (UInt64, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_timestamp(ptr, error) - }) - return (result, errorCode != 0 ? PendingInboundTxError.generic(errorCode) : nil) - } - - var sourcePublicKey: (PublicKey?, Error?) { - var errorCode: Int32 = -1 - let resultPointer = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_source_public_key(ptr, error) - }) - guard errorCode == 0 else { - return (nil, PendingInboundTxError.generic(errorCode)) - } - - return (PublicKey(pointer: resultPointer!), nil) - } - - var status: (TxStatus, Error?) { - var errorCode: Int32 = -1 - let statusCode: Int32 = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transaction_get_status(ptr, error) - }) - guard errorCode == 0 else { - return (.unknown, PendingInboundTxError.generic(errorCode)) - } - - return (statusFrom(code: statusCode), nil) - } - - var contact: (Contact?, Error?) { - if cachedContact != nil { - return (cachedContact, nil) - } - - guard let wallet = TariLib.shared.tariWallet else { - return (nil, WalletErrors.walletNotInitialized) - } - - let (contacts, contactsError) = wallet.contacts - guard contactsError == nil else { - return (nil, contactsError) - } - - let (pubKey, pubKeyError) = sourcePublicKey - guard pubKeyError == nil else { - return (nil, pubKeyError) - } - - do { - cachedContact = try contacts!.find(publicKey: pubKey!) - return (cachedContact, nil) - } catch { - return (nil, error) - } - } - - init(pendingInboundTxPointer: OpaquePointer) { - ptr = pendingInboundTxPointer - } - - deinit { - pending_inbound_transaction_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTxs.swift b/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTxs.swift deleted file mode 100644 index 214a6910..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/PendingInboundTxs.swift +++ /dev/null @@ -1,111 +0,0 @@ -// PendingInboundTxs.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/18 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PendingInboundTxsErrors: Error { - case pendingInboundTxNotFound - case generic(_ errorCode: Int32) -} - -class PendingInboundTxs: TxsProtocol { - typealias Tx = PendingInboundTx - - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var count: (UInt32, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transactions_get_length(ptr, error)}) - - return (result, errorCode != 0 ? PendingInboundTxsErrors.generic(errorCode) : nil) - } - - init(pendingInboundTxsPointer: OpaquePointer) { - ptr = pendingInboundTxsPointer - } - - var list: ([PendingInboundTx], Error?) { - let (count, countError) = self.count - guard countError == nil else { - return ([], countError) - } - - var list: [PendingInboundTx] = [] - - if count > 0 { - for n in 0...count - 1 { - do { - let tx = try self.at(position: n) - list.append(tx) - } catch { - return ([], error) - } - } - } - - let sortedList = list.sorted(by: { $0.date.0?.compare($1.date.0!) == .orderedDescending }) - - return (sortedList, nil) - } - - func at(position: UInt32) throws -> PendingInboundTx { - var errorCode: Int32 = -1 - let pendingInboundTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - pending_inbound_transactions_get_at(ptr, position, error)}) - guard errorCode == 0 else { - throw PendingInboundTxsErrors.generic(errorCode) - } - - if pendingInboundTxPointer == nil { - throw PendingInboundTxsErrors.pendingInboundTxNotFound - } - - return PendingInboundTx(pendingInboundTxPointer: pendingInboundTxPointer!) - } - - deinit { - pending_inbound_transactions_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTx.swift b/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTx.swift deleted file mode 100644 index e8b0a6f0..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTx.swift +++ /dev/null @@ -1,170 +0,0 @@ -// PendingOutboundTx.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/18 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PendingOutboundTxError: Error { - case generic(_ errorCode: Int32) -} - -class PendingOutboundTx: TxProtocol { - var cachedContact: Contact? - - var sourcePublicKey: (PublicKey?, Error?) - var transactionKernel: (CompletedTxKernel?, Error?) - - private var ptr: OpaquePointer - - var direction: TxDirection = .outbound - - var pointer: OpaquePointer { - return ptr - } - - var id: (UInt64, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_transaction_id(ptr, error) - }) - return (result, errorCode != 0 ? PendingOutboundTxError.generic(errorCode) : nil) - } - - var microTari: (MicroTari?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_amount(ptr, error) - }) - - guard errorCode == 0 else { - return (nil, CompletedTxError.generic(errorCode)) - } - - return (MicroTari(result), nil) - } - - var fee: (MicroTari?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_fee(ptr, error) - }) - return (MicroTari(result), errorCode != 0 ? CompletedTxError.generic(errorCode) : nil) - } - - var message: (String, Error?) { - var errorCode: Int32 = -1 - let resultPtr = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_message(ptr, error) - }) - let result = String(cString: resultPtr!) - - let mutable = UnsafeMutablePointer(mutating: resultPtr!) - string_destroy(mutable) - - return (result, errorCode != 0 ? PendingOutboundTxError.generic(errorCode) : nil) - } - - var timestamp: (UInt64, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_timestamp(ptr, error) - }) - return (result, errorCode != 0 ? PendingOutboundTxError.generic(errorCode) : nil) - } - - var destinationPublicKey: (PublicKey?, Error?) { - var errorCode: Int32 = -1 - let resultPointer = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_destination_public_key(ptr, error) - }) - guard errorCode == 0 else { - return (nil, PendingOutboundTxError.generic(errorCode)) - } - - return (PublicKey(pointer: resultPointer!), nil) - } - - var status: (TxStatus, Error?) { - var errorCode: Int32 = -1 - let statusCode: Int32 = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transaction_get_status(ptr, error) - - }) - guard errorCode == 0 else { - return (.unknown, PendingOutboundTxError.generic(errorCode)) - } - - return (statusFrom(code: statusCode), nil) - } - - var contact: (Contact?, Error?) { - if cachedContact != nil { - return (cachedContact, nil) - } - - guard let wallet = TariLib.shared.tariWallet else { - return (nil, WalletErrors.walletNotInitialized) - } - - let (contacts, contactsError) = wallet.contacts - guard contactsError == nil else { - return (nil, contactsError) - } - - let (pubKey, pubKeyError) = destinationPublicKey - guard pubKeyError == nil else { - return (nil, pubKeyError) - } - - do { - cachedContact = try contacts!.find(publicKey: pubKey!) - return (cachedContact, nil) - } catch { - return (nil, error) - } - } - - init(pendingOutboundTxPointer: OpaquePointer) { - ptr = pendingOutboundTxPointer - } - - deinit { - pending_outbound_transaction_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTxs.swift b/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTxs.swift deleted file mode 100644 index 94e08a14..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/PendingOutboundTxs.swift +++ /dev/null @@ -1,112 +0,0 @@ -// PendingOutboundTxs.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/18 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum PendingOutboundTxsErrors: Error { - case pendingOutboundTxNotFound - case generic(_ errorCode: Int32) -} - -class PendingOutboundTxs: TxsProtocol { - typealias Tx = PendingOutboundTx - - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var count: (UInt32, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transactions_get_length(ptr, error) - }) - return (result, errorCode != 0 ? PendingOutboundTxsErrors.generic(errorCode) : nil) - } - - var list: ([PendingOutboundTx], Error?) { - let (count, countError) = self.count - guard countError == nil else { - return ([], countError) - } - - var list: [PendingOutboundTx] = [] - - if count > 0 { - for n in 0...count - 1 { - do { - let tx = try self.at(position: n) - list.append(tx) - } catch { - return ([], error) - } - } - } - - let sortedList = list.sorted(by: { $0.date.0?.compare($1.date.0!) == .orderedDescending }) - - return (sortedList, nil) - } - - init(pendingOutboundTxsPointer: OpaquePointer) { - ptr = pendingOutboundTxsPointer - } - - func at(position: UInt32) throws -> PendingOutboundTx { - var errorCode: Int32 = -1 - let pendingOutboundTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - pending_outbound_transactions_get_at(ptr, position, error) - }) - guard errorCode == 0 else { - throw PendingOutboundTxsErrors.generic(errorCode) - } - - if pendingOutboundTxPointer == nil { - throw PendingOutboundTxsErrors.pendingOutboundTxNotFound - } - - return PendingOutboundTx(pendingOutboundTxPointer: pendingOutboundTxPointer!) - } - - deinit { - pending_outbound_transactions_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxProtocol.swift b/MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxProtocol.swift deleted file mode 100644 index 534a9164..00000000 --- a/MobileWallet/TariLib/Wrappers/Transactions/Protocols/TxProtocol.swift +++ /dev/null @@ -1,138 +0,0 @@ -// TxProtocol.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/01/15 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum TxDirection { - case inbound - case outbound - case none -} - -enum TxStatus: Error { - case txNullError - case completed - case broadcast - case minedUnconfirmed - case imported - case pending - case minedConfirmed - case rejected - case fauxUnconfirmed - case fauxConfirmed - case unknown -} - -protocol TxProtocol { - var pointer: OpaquePointer { get } - var id: (UInt64, Error?) { get } - var microTari: (MicroTari?, Error?) { get } - // var fee: (UInt64, Error?) { get } - var message: (String, Error?) { get } - var timestamp: (UInt64, Error?) { get } - var sourcePublicKey: (PublicKey?, Error?) { get } - var status: (TxStatus, Error?) { get } - var destinationPublicKey: (PublicKey?, Error?) { get } - var transactionKernel: (CompletedTxKernel?, Error?) { get } - var direction: TxDirection { get } - var contact: (Contact?, Error?) { get } -} - -extension TxProtocol { - var date: (Date?, Error?) { - let (timestamp, error) = self.timestamp - if error != nil { - return (nil, error) - } - - return (Date(timeIntervalSince1970: Double(timestamp)), nil) - } - - func statusFrom(code: Int32) -> TxStatus { - switch code { - case -1: - return .txNullError - case 0: - return .completed - case 1: - return .broadcast - case 2: - return .minedUnconfirmed - case 3: - return .imported - case 4: - return .pending - case 6: - return .minedConfirmed - case 7: - return .rejected - case 8: - return .fauxUnconfirmed - case 9: - return .fauxConfirmed - default: - return .unknown - } - } - - var isCancelled: Bool { - if let completedTx = self as? CompletedTx { - return completedTx.isCancelled - } - return false - } - - var isPending: Bool { - if let _ = self as? PendingInboundTx { - return true - } - - if let _ = self as? PendingOutboundTx { - return true - } - let (status, error) = self.status - if error != nil { fatalError() } - return status == .minedUnconfirmed - } - - var isOneSidedPayment: Bool { - status.0 == .fauxConfirmed || status.0 == .fauxUnconfirmed - } -} diff --git a/MobileWallet/TariLib/Wrappers/TransportConfig.swift b/MobileWallet/TariLib/Wrappers/TransportConfig.swift deleted file mode 100644 index 838f8058..00000000 --- a/MobileWallet/TariLib/Wrappers/TransportConfig.swift +++ /dev/null @@ -1,118 +0,0 @@ -// TransportType.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/14 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum TransportTypeError: Error { - case generic(_ errorCode: Int32) -} - -class TransportConfig { - private var ptr: OpaquePointer - - var pointer: OpaquePointer { - return ptr - } - - var address: (String, Error?) { - var errorCode: Int32 = -1 - let resultPtr = withUnsafeMutablePointer(to: &errorCode, { error in - transport_memory_get_address(ptr, error)}) - let result = String(cString: resultPtr!) - return (result, errorCode != 0 ? ByteVectorError.generic(errorCode) : nil ) - } - - init() { - let result = transport_memory_create() - ptr = result! - } - - init(listenerAddress: String) throws { - var errorCode: Int32 = -1 - let result = listenerAddress.withCString({ chars in - withUnsafeMutablePointer(to: &errorCode, { error in - transport_tcp_create(chars, error)}) - }) - guard errorCode == 0 else { - throw TransportTypeError.generic(errorCode) - } - ptr = result! - } - - init( - controlServerAddress: String, - torPort: Int, - torCookie: ByteVector, - socksUsername: String = "", - socksPassword: String = "" - ) throws { - var errorCode: Int32 = -1 - - let result = controlServerAddress.withCString({control in - socksUsername.withCString({ user in - socksPassword.withCString({ pass in - withUnsafeMutablePointer(to: &errorCode, { error in - transport_tor_create( - controlServerAddress.count > 0 ? control : nil, - torCookie.pointer, - UInt16(torPort), - false, - socksUsername.count > 0 ? user : nil, - socksPassword.count > 0 ? pass : nil, - error - ) - }) - }) - }) - }) - guard errorCode == 0 else { - throw TransportTypeError.generic(errorCode) - } - ptr = result! - } - - init (pointer: OpaquePointer) { - ptr = pointer - } - - deinit { - transport_type_destroy(ptr) - } -} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift b/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift index a7c506bd..dc2738b8 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Connection Monitor/ConnectionMonitor.swift @@ -38,70 +38,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Reachability import UIKit import Combine final class ConnectionMonitor { - struct StatusModel { - let networkConnection: NetworkMonitor.Status - let torConnection: TorMonitor.Status - let baseNodeConnectivity: BaseNodeConnectivityStatus - let syncStatus: BaseNodeStatusMonitor.SyncStatus - } - // MARK: - Properties - static let shared = ConnectionMonitor() - - @Published private(set) var status: StatusModel? + @Published private(set) var networkConnection: NetworkMonitor.Status = .disconnected + @Published private(set) var torConnection: TorManager.ConnectionStatus = .disconnected + @Published private(set) var torBootstrapProgress: Int = 0 + @Published private(set) var isTorBootstrapCompleted: Bool = false + @Published private(set) var baseNodeConnection: BaseNodeConnectivityStatus = .offline + @Published private(set) var syncStatus: TariValidationService.SyncStatus = .idle private let networkMonitor = NetworkMonitor() - private let torMonitor = TorMonitor() - private let baseNodeStatusMonitor = BaseNodeStatusMonitor() - private var cancellables = Set() - // MARK: - Initialisers - - private init() { - setupCallbacks() - } - // MARK: - Setups - private func setupCallbacks() { - Publishers.CombineLatest4(networkMonitor.$status, torMonitor.$status, baseNodeStatusMonitor.$connectionStatus, baseNodeStatusMonitor.$syncStatus) - .map { StatusModel(networkConnection: $0, torConnection: $1, baseNodeConnectivity: $2, syncStatus: $3) } - .assign(to: \.status, on: self) + func setupPublishers(torConnectionStatus: AnyPublisher, torBootstrapProgress: AnyPublisher, + baseNodeConnectionStatus: AnyPublisher, baseNodeSyncStatus: AnyPublisher) { + + networkMonitor.$status + .assign(to: \.networkConnection, on: self) .store(in: &cancellables) - } - - // MARK: - Actions - - func showDetailsPopup() { - let headerSection = PopUpHeaderView() - let contentSection = PopUpNetworkStatusContentView() - let buttonsSection = PopUpButtonsView() + torConnectionStatus + .assign(to: \.torConnection, on: self) + .store(in: &cancellables) - headerSection.label.text = localized("connection_status.popUp.header") + torBootstrapProgress + .assign(to: \.torBootstrapProgress, on: self) + .store(in: &cancellables) - let cancellable = $status - .compactMap { $0 } - .receive(on: DispatchQueue.main) - .sink { [weak contentSection] in - contentSection?.updateNetworkStatus(text: $0.networkConnection.statusName, statusColor: $0.networkConnection.statusColor) - contentSection?.updateTorStatus(text: $0.torConnection.statusName, statusColor: $0.torConnection.statusColor) - contentSection?.updateBaseNodeConnectionStatus(text: $0.baseNodeConnectivity.statusName, statusColor: $0.baseNodeConnectivity.statusColor) - contentSection?.updateBaseNodeSyncStatus(text: $0.syncStatus.statusName, statusColor: $0.syncStatus.statusColor) - } + torBootstrapProgress + .map { $0 >= 100 } + .assign(to: \.isTorBootstrapCompleted, on: self) + .store(in: &cancellables) - buttonsSection.addButton(model: PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { PopUpPresenter.dismissPopup { [weak cancellable] in cancellable?.cancel() }})) + baseNodeConnectionStatus + .assign(to: \.baseNodeConnection, on: self) + .store(in: &cancellables) - let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) - PopUpPresenter.show(popUp: popUp, configuration: .dialog(hapticType: .none)) + baseNodeSyncStatus + .assign(to: \.syncStatus, on: self) + .store(in: &cancellables) } } @@ -126,31 +108,27 @@ private extension NetworkMonitor.Status { } } -private extension TorMonitor.Status { +private extension TorManager.ConnectionStatus { var statusName: String { switch self { - case .disconnected: + case .disconnected, .disconnecting: return localized("connection_status.popUp.label.tor_status.disconnected") - case .connecting: + case .connecting, .portsOpen: return localized("connection_status.popUp.label.tor_status.connecting") case .connected: return localized("connection_status.popUp.label.tor_status.connected") - case .failed: - return localized("connection_status.popUp.label.tor_status.failed") } } var statusColor: UIColor? { switch self { - case .disconnected: + case .disconnected, .disconnecting: return .tari.system.red - case .connecting: + case .connecting, .portsOpen: return .tari.system.orange case .connected: return .tari.system.green - case .failed: - return .tari.system.red } } } @@ -180,17 +158,17 @@ private extension BaseNodeConnectivityStatus { } } -private extension BaseNodeStatusMonitor.SyncStatus { +private extension TariValidationService.SyncStatus { var statusName: String { switch self { case .idle: return localized("connection_status.popUp.label.base_node_sync.idle") - case .pending: + case .syncing: return localized("connection_status.popUp.label.base_node_sync.pending") - case .success: + case .synced: return localized("connection_status.popUp.label.base_node_sync.success") - case .failure: + case .failed: return localized("connection_status.popUp.label.base_node_sync.failure") } } @@ -199,195 +177,62 @@ private extension BaseNodeStatusMonitor.SyncStatus { switch self { case .idle: return .tari.system.red - case .pending: + case .syncing: return .tari.system.orange - case .success: + case .synced: return .tari.system.green - case .failure: + case .failed: return .tari.system.red } } } -enum ConnectionMonitorStateReachability: String { - case cellular = "Cellular โœ…" - case wifi = "WiFi โœ…" - case offline = "Offline โŒ" - case unknown = "Unknown โ“" -} - -enum ConnectionMonitorStateTor: String { - case connected = "Connected โœ…" - case failed = "Failed โŒ" - case connecting = "Connecting โŒ›" - case unknown = "Unknown โ“" -} - -enum ConnectionMonitorStateBaseNode: String { - case notInited = "Not initialized" - case pending = "Pending sync โŒ›" - case failure = "Sync failed โŒ" - case success = "Synced โœ…" -} - -final class ConnectionMonitorState { - var reachability: ConnectionMonitorStateReachability = .unknown { didSet { onUpdate() } } - var torBootstrapProgress: Int = 0 { didSet { onUpdate() } } - var torPortsOpen = false { didSet { onUpdate() } } - var torPortsOpenDisplay: String { - return torPortsOpen ? "Open โœ…" : "Closed โŒ" - } - var torStatus: ConnectionMonitorStateTor = .unknown { didSet { onUpdate() } } - var baseNodeSyncStatus: ConnectionMonitorStateBaseNode = ConnectionMonitorStateBaseNode.notInited { didSet { onUpdate() } } - - var currentBaseNodeName: String { NetworkManager.shared.selectedNetwork.selectedBaseNode.name } - var baseNodeConnectivityStatus: BaseNodeConnectivityStatus? { didSet { onUpdate() } } - +extension ConnectionMonitor { + var formattedDisplayItems: [String] { var entries: [String] = [] - entries.append("Reachability: \(reachability.rawValue)") - entries.append("Base node (\(currentBaseNodeName)): \(baseNodeSyncStatus.rawValue)") - entries.append("Base node connection status: \(baseNodeConnectivityStatus?.name ?? "Unknown โ“")") - entries.append("Tor ports: \(torPortsOpenDisplay)") - entries.append("Tor status: \(torStatus.rawValue)") + entries.append("Reachability: \(networkConnection.statusName)") + entries.append("Base node (\(NetworkManager.shared.selectedNetwork.selectedBaseNode.name)): \(syncStatus.statusName)") + entries.append("Base node connection status: \(baseNodeConnection.statusName)") + entries.append("Tor status: \(torConnection.statusName)") entries.append("Tor bootstrap progress: \(torBootstrapProgress)%") - + return entries } - - // ALlow other components to subscribe to connection state changes from one place - private func onUpdate() { - TariEventBus.postToMainThread(.connectionMonitorStatusChanged, sender: self) - } -} - -@available(*, deprecated, message: "This monitor will be removed in the near future. Please Use ConnectionMonitor instead") -class LegacyConnectionMonitor { - public static let shared = LegacyConnectionMonitor() - private var reachability: Reachability? - - var state = ConnectionMonitorState() - - private var cancellables = Set() - - private init() { - do { - reachability = try Reachability() - } catch { - TariLogger.error("Failed to init Reachability. Network status not being monitored", error: error) - } - } - - func start() { - state = ConnectionMonitorState() // Reset state to defaults - startMonitoringNetwork() - startMonitoringTor() - startMonitoringBaseNodeSync() - TariLogger.verbose("Started monitoring network connections") - } - - private func startMonitoringNetwork() { - guard let reachability = self.reachability else { - return - } - - self.setReachability(reachability) - - reachability.whenReachable = { [weak self] reachability in - guard let self = self else { return } - - self.setReachability(reachability) - } - - reachability.whenUnreachable = { [weak self] _ in - guard let self = self else { return } - self.state.reachability = .offline - } - - do { - try reachability.startNotifier() - } catch { - TariLogger.error("Failed to start Reachability notifier", error: error) - } - } - - private func setReachability(_ reachability: Reachability) { - switch reachability.connection { - case .cellular: - self.state.reachability = .cellular - case .wifi: - self.state.reachability = .wifi - default: - self.state.reachability = .unknown - } - } - - private func startMonitoringTor() { - TariEventBus.onMainThread(self, eventType: .torPortsOpened) { [weak self] (_) in - guard let self = self else { return } - self.state.torPortsOpen = true - } - - TariEventBus.onMainThread(self, eventType: .torConnected) { [weak self] (_) in - guard let self = self else { return } - - // TODO check torController.isConnected as well - self.state.torStatus = .connected - } - - TariEventBus.onMainThread(self, eventType: .torConnectionFailed) { [weak self] (_) in - guard let self = self else { return } - self.state.torStatus = .failed - } - - TariEventBus.onMainThread(self, eventType: .torConnectionProgress) { - [weak self] - (result) in - guard let self = self else { return } - if let progress: Int = result?.object as? Int { - self.state.torBootstrapProgress = progress - self.state.torStatus = .connecting - } - } - } - - private func startMonitoringBaseNodeSync() { - - TariEventBus.onMainThread(self, eventType: .baseNodeSyncStarted) { [weak self] _ in - self?.state.baseNodeSyncStatus = .pending - } - - TariEventBus.onMainThread(self, eventType: .baseNodeSyncComplete) { [weak self] result in - guard let result = result?.object as? [String: Any] else { return } - guard let isSuccess = result["success"] as? Bool, isSuccess else { - self?.state.baseNodeSyncStatus = .failure - return - } - self?.state.baseNodeSyncStatus = .success - } - - TariEventBus.events(forType: .connectionStatusChanged) - .map { $0.object as? BaseNodeConnectivityStatus } - .assign(to: \.baseNodeConnectivityStatus, on: state) + + func showDetailsPopup() { + + let headerSection = PopUpHeaderView() + let contentSection = PopUpNetworkStatusContentView() + let buttonsSection = PopUpButtonsView() + + headerSection.label.text = localized("connection_status.popUp.header") + + var cancellables = Set() + + $networkConnection + .receive(on: DispatchQueue.main) + .sink { [weak contentSection] in contentSection?.updateNetworkStatus(text: $0.statusName, statusColor: $0.statusColor) } .store(in: &cancellables) - } - - func stop() { - reachability?.stopNotifier() - TariEventBus.unregister(self) - TariLogger.verbose("Stopped monitoring network connections") - } -} - -private extension BaseNodeConnectivityStatus { - var name: String { - switch self { - case .offline: - return "Disconnected โŒ" - case .connecting: - return "Connecting โŒ›" - case .online: - return "Connected โœ…" - } + + $torConnection + .receive(on: DispatchQueue.main) + .sink { [weak contentSection] in contentSection?.updateTorStatus(text: $0.statusName, statusColor: $0.statusColor) } + .store(in: &cancellables) + + $baseNodeConnection + .receive(on: DispatchQueue.main) + .sink { [weak contentSection] in contentSection?.updateBaseNodeConnectionStatus(text: $0.statusName, statusColor: $0.statusColor) } + .store(in: &cancellables) + + $syncStatus + .receive(on: DispatchQueue.main) + .sink { [weak contentSection] in contentSection?.updateBaseNodeSyncStatus(text: $0.statusName, statusColor: $0.statusColor) } + .store(in: &cancellables) + + buttonsSection.addButton(model: PopUpDialogButtonModel(title: localized("common.close"), type: .text, callback: { PopUpPresenter.dismissPopup { cancellables.forEach { $0.cancel() }}})) + + let popUp = TariPopUp(headerSection: headerSection, contentSection: contentSection, buttonsSection: buttonsSection) + PopUpPresenter.show(popUp: popUp, configuration: .dialog(hapticType: .none)) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/ErrorDescriptions.swift b/MobileWallet/TariLib/Wrappers/Utils/ErrorDescriptions.swift deleted file mode 100644 index 66667c08..00000000 --- a/MobileWallet/TariLib/Wrappers/Utils/ErrorDescriptions.swift +++ /dev/null @@ -1,114 +0,0 @@ -// ErrorDescriptions.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/01/29 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -extension WalletErrors: LocalizedError { - public var errorDescription: String? { - switch self { - case .generic(let code): - // TODO create a map of Tari errors from rust - return "\(localized("wallet.error.generic_code")) \(code)." - case .insufficientFunds(let microTariSpendable): - return "\(localized("wallet.error.insufficient_funds")) \(microTariSpendable.formatted)." - case .addUpdateContact: - return localized("wallet.error.add_update_contact") - case .removeContact: - return localized("wallet.error.remove_contact") - case .addOwnContact: - return localized("wallet.error.add_own_contact") - case .invalidPublicKeyHex: - return localized("wallet.error.invalid_public_key_hex") - case .generateTestData: - return localized("wallet.error.test_data") - case .generateTestReceiveTx: - return localized("wallet.error.test_receieve_tx") - case .sendingTx: - return localized("wallet.error.send_tx") - case .testTxBroadcast: - return localized("wallet.error.broadcast") - case .testTxMined: - return localized("wallet.error.mine_tx") - case .testSendCompleteTx: - return localized("wallet.error.test_tx") - case .completedTxById: - return localized("wallet.error.find_completed_tx") - case .cancelledTxById: - return localized("wallet.error.find_canceled_tx") - case .walletNotInitialized: - return localized("wallet.error.wallet_not_initialized") - case .invalidSignatureAndNonceString: - return localized("wallet.error.invalid_signature") - case .cancelNonPendingTx: - return localized("wallet.error.cancel_non_pending_tx") - case .txToCancel: - return localized("wallet.error.fetch_txs_to_cancel") - case .notEnoughFunds: - return nil - case .fundsPending: - return nil - } - } -} - -extension KeyServerError: LocalizedError { - public var errorDescription: String? { - switch self { - case .server(let statusCode, let message): - if message != nil { - return message - } - - return localized("key_server.error.server") + " \(statusCode)." - case .unknown: - return localized("key_server.error.unknown") - case .invalidSignature: - return localized("key_server.error.invalid_signature") - case .tooManyAllocationRequests: - return localized("key_server.error.too_many_allocation_requests") - case .missingResponse: - return localized("key_server.error.missing_response") - case .responseInvalid: - return localized("key_server.error.response_invalid") - } - } -} - -// TODO add the other error enums diff --git a/MobileWallet/TariLib/Wrappers/Utils/KeyServer.swift b/MobileWallet/TariLib/Wrappers/Utils/KeyServer.swift index 240ea6cb..8fe1383a 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/KeyServer.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/KeyServer.swift @@ -70,7 +70,7 @@ final class KeyServer { NetworkManager.shared.selectedNetwork.tickerSymbol, NetworkManager.shared.selectedNetwork.tickerSymbol ) - private let signature: Signature + private let messageMetadata: MessageMetadata private let url: URL? private let jsonEncoder: JSONEncoder = { @@ -80,23 +80,11 @@ final class KeyServer { }() init() throws { - guard let wallet = TariLib.shared.tariWallet else { - throw WalletErrors.walletNotInitialized - } - - let (publicKey, publicKeyError) = wallet.publicKey - guard publicKeyError == nil else { - throw publicKeyError! - } - - let (publicKeyHex, hexError) = publicKey!.hex - guard hexError == nil else { - throw hexError! - } + let publicKeyHex = try Tari.shared.walletPublicKey.byteVector.hex let message = "\(MESSAGE_PREFIX) \(publicKeyHex)" - - self.signature = try wallet.signMessage(message) + + messageMetadata = try Tari.shared.faucet.sign(message: message) self.url = NetworkManager.shared.selectedNetwork.faucetURL? .appendingPathComponent("free_tari/allocate_max") @@ -105,26 +93,16 @@ final class KeyServer { func requestDrop(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) throws { - guard let wallet = TariLib.shared.tariWallet, let url = self.url else { return } + guard let url = url else { return } guard KeyServer.isRequestInProgress == false else { - TariLogger.warn("Key server request already in progress") + Logger.log(message: "Key server request already in progress", domain: .general, level: .warning) return } KeyServer.isRequestInProgress = true - - let (completedTxs, completedTxsError) = wallet.completedTxs - guard completedTxs != nil else { - KeyServer.isRequestInProgress = false - throw completedTxsError! - } - - let (completedTxsCount, completedTxsCountError) = completedTxs!.count - guard completedTxsCountError == nil else { - KeyServer.isRequestInProgress = false - throw completedTxsCountError! - } + + let completedTxsCount = Tari.shared.transactions.completed.count // If the user has a completed, just ignore this request as it's not a fresh install guard completedTxsCount == 0 else { @@ -138,8 +116,8 @@ final class KeyServer { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try jsonEncoder.encode( KeyServerRequest( - signature: signature.hex, - publicNonce: signature.nonce, + signature: messageMetadata.hex, + publicNonce: messageMetadata.nonce, network: NetworkManager.shared.selectedNetwork.name ) ) @@ -229,21 +207,7 @@ final class KeyServer { } do { - let utxo = UTXO( - privateKeyHex: key1, - value: value1, - message: self.TARIBOT_MESSAGE1, - sourcePublicKeyHex: returnPubKeyHex, - publicNonce: publicNonce1, - uValue: uValue1, - vValue: vValue1, - senderOffsetPublicKeyHex: senderOffsetPublicKey1 - ) - - // Add TariBot as a contact - try wallet.addUpdateContact(alias: "TariBot", publicKeyHex: utxo.sourcePublicKeyHex) - try wallet.importUtxo(utxo) - + try self.importUtxo(sourcePublicKeyHex: returnPubKeyHex, spendingKeyHex: key1, nonce: publicNonce1, uData: uValue1, vData: vValue1, senderOffsetPublicKeyHex: senderOffsetPublicKey1, amount: value1, message: self.TARIBOT_MESSAGE1) } catch { onRequestError(error) return @@ -290,71 +254,92 @@ final class KeyServer { task.resume() } + + private func importUtxo(sourcePublicKeyHex: String, spendingKeyHex: String, nonce: Data, uData: Data, vData: Data, senderOffsetPublicKeyHex: String, amount: UInt64, message: String) throws { + + let sourcePublicKey = try PublicKey(hex: sourcePublicKeyHex) + let contact = try Contact(alias: "TariBot", publicKeyPointer: sourcePublicKey.pointer) + try Tari.shared.contacts.upsert(contact: contact) + + let spendingKey = try PrivateKey(hex: spendingKeyHex) + let signaturePointer = try Tari.shared.faucet.commitmentSignature(publicNonce: nonce, u: uData, v: vData) + let senderOffsetPublicKey = try PublicKey(hex: senderOffsetPublicKeyHex) + + try Tari.shared.faucet.importUtxo( + amount: amount, + spendingKey: spendingKey, + sourcePublicKey: sourcePublicKey, + metadataSignaturePointer: signaturePointer, + senderOffsetPublicKey: senderOffsetPublicKey, + scriptPrivateKey: spendingKey, + message: message + ) + } private func hasSentATx() -> Bool { - guard let wallet = TariLib.shared.tariWallet else { - return false - } - - guard let (pendingOutboundTxs) = wallet.pendingOutboundTxs.0 else { - TariLogger.error("Failed to load pendingOutboundTxs") - return false - } - - if pendingOutboundTxs.count.0 > 0 { - return true - } - - guard let (completedTxs) = wallet.completedTxs.0 else { - TariLogger.error("Failed to load completedTxs") - return false - } - - let completedCount = completedTxs.count.0 - guard completedCount > 0 else { - return false - } - - for n in 0...completedCount - 1 { - do { - let tx = try completedTxs.at(position: n) - if tx.direction == .outbound { - return true - } - } catch { - TariLogger.error("Failed to load completed tx", error: error) - } + + guard Tari.shared.transactions.pendingOutbound.isEmpty else { return true } + + do { + return try Tari.shared.transactions.completed.contains { try $0.isOutboundTransaction } + } catch { + Logger.log(message: "Failed to load completed tx: \(error.localizedDescription)", domain: .general, level: .error) } - + return false } func importSecondUtxo(onComplete: @escaping (() -> Void)) throws { - guard let wallet = TariLib.shared.tariWallet else { - return - } - - if let data = UserDefaults.standard.value(forKey: KeyServer.secondUtxoStorageKey) as? Data { - guard hasSentATx() else { - return - } - - do { - let utxo = try PropertyListDecoder().decode(UTXO.self, from: data) - TariLogger.info("Importing stored 2nd utxo") - - try wallet.importUtxo(utxo) - - UserDefaults.standard.removeObject(forKey: KeyServer.secondUtxoStorageKey) - onComplete() - } catch { - TariLogger.error("Unable to load stored UTXO", error: error) - } + + guard let data = UserDefaults.standard.value(forKey: KeyServer.secondUtxoStorageKey) as? Data, hasSentATx() else { return } + + do { + let utxo = try PropertyListDecoder().decode(UTXO.self, from: data) + Logger.log(message: "Importing stored 2nd utxo", domain: .general, level: .info) + + try importUtxo( + sourcePublicKeyHex: utxo.sourcePublicKeyHex, + spendingKeyHex: utxo.privateKeyHex, + nonce: utxo.publicNonce, + uData: utxo.uValue, + vData: utxo.vValue, + senderOffsetPublicKeyHex: utxo.senderOffsetPublicKeyHex, + amount: utxo.value, + message: utxo.message + ) + + UserDefaults.standard.removeObject(forKey: KeyServer.secondUtxoStorageKey) + onComplete() + } catch { + Logger.log(message: "Unable to load stored UTXO: \(error.localizedDescription)", domain: .general, level: .error) } } private func storeUtxo(utxo: UTXO) throws { - TariLogger.verbose("Storing UTXO for later use") + Logger.log(message: "Storing UTXO for later use", domain: .general, level: .info) UserDefaults.standard.set(try? PropertyListEncoder().encode(utxo), forKey: KeyServer.secondUtxoStorageKey) } } + +extension KeyServerError: LocalizedError { + public var errorDescription: String? { + switch self { + case .server(let statusCode, let message): + if message != nil { + return message + } + + return localized("key_server.error.server") + " \(statusCode)." + case .unknown: + return localized("key_server.error.unknown") + case .invalidSignature: + return localized("key_server.error.invalid_signature") + case .tooManyAllocationRequests: + return localized("key_server.error.too_many_allocation_requests") + case .missingResponse: + return localized("key_server.error.missing_response") + case .responseInvalid: + return localized("key_server.error.response_invalid") + } + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/BaseNodeManager.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift similarity index 77% rename from MobileWallet/TariLib/Wrappers/Utils/BaseNodeManager.swift rename to MobileWallet/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift index d53cbc19..0606b0d5 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/BaseNodeManager.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/ConsoleLogger.swift @@ -1,10 +1,10 @@ -// BaseNodeManager.swift +// ConsoleLogger.swift /* Package MobileWallet - Created by Adrian Truszczynski on 02/03/2022 + Created by Adrian Truszczynski on 11/10/2022 Using Swift 5.0 - Running on macOS 12.1 + Running on macOS 12.6 Copyright 2019 The Tari Project @@ -38,12 +38,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -enum BaseNodeManager { +import os + +final class ConsoleLogger {} + +extension ConsoleLogger: Logable { - static func addBaseNode(name: String, peer: String) throws { - let node = try BaseNode(name: name, peer: peer) - try TariLib.shared.update(baseNode: node, syncAfterSetting: false) - NetworkManager.shared.selectedNetwork.customBaseNodes.append(node) - NetworkManager.shared.selectedNetwork.selectedBaseNode = node + func log(message: String, domain: Logger.Domain, logLevel: Logger.Level) { + let formattedMessage = LogFormatter.formattedMessage(message: message, domain: domain, logLevel: logLevel, showPrefix: false) + os_log("%@", formattedMessage) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift new file mode 100644 index 00000000..2157bcbf --- /dev/null +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/CrashLogger.swift @@ -0,0 +1,78 @@ +// CrashLogger.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 11/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Sentry + +final class CrashLogger { + + init() { + guard let sentryPublicDSN = TariSettings.shared.sentryPublicDSN else { return } + let options = Options() + options.dsn = sentryPublicDSN + SentrySDK.start(options: options) + } +} + +extension CrashLogger: Logable { + + func log(message: String, domain: Logger.Domain, logLevel: Logger.Level) { + + let breadcrumb = Breadcrumb(level: logLevel.sentryLevel, category: domain.name) + breadcrumb.message = message + + SentrySDK.addBreadcrumb(crumb: breadcrumb) + } +} + +private extension Logger.Level { + + var sentryLevel: SentryLevel { + switch self { + case .verbose: + return .debug + case .info: + return .info + case .warning: + return .warning + case .error: + return .error + } + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/FileLogger.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/FileLogger.swift new file mode 100644 index 00000000..4893b9c6 --- /dev/null +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/FileLogger.swift @@ -0,0 +1,49 @@ +// FileLogger.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 11/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +final class FileLogger {} + +extension FileLogger: Logable { + + func log(message: String, domain: Logger.Domain, logLevel: Logger.Level) { + let formattedMessage = LogFormatter.formattedMessage(message: message, domain: domain, logLevel: logLevel, showPrefix: true) + Tari.shared.log(message: formattedMessage) + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift new file mode 100644 index 00000000..2cc1a770 --- /dev/null +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/LogFormatter.swift @@ -0,0 +1,84 @@ +// LogFormatter.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 11/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +enum LogFormatter { + + private static let appNamePrefix = "[Aurora]" + private static let domainNameLength = Logger.Domain.allCases.map { $0.name.count }.max() ?? 0 + private static let levelNameLength = Logger.Level.allCases.map { $0.name.count }.max() ?? 0 + + static func formattedMessage(message: String, domain: Logger.Domain, logLevel: Logger.Level, showPrefix: Bool) -> String { + + let domainName = domain.name.fixedLength(domainNameLength) + let logLevelName = logLevel.name.fixedLength(levelNameLength) + var components = [domainName, logLevelName, message] + + if showPrefix { + components.insert(appNamePrefix, at: 0) + } + + return components.joined(separator: " | ") + } +} + +private extension String { + + func fixedLength(_ length: Int) -> Self { + var result = prefix(length) + while result.count < length { result += " " } + return String(result) + } +} + +private extension Logger.Level { + + var name: String { + switch self { + case .verbose: + return "VERBOSE" + case .info: + return "INFO" + case .warning: + return "WARNING" + case .error: + return "ERROR" + } + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift new file mode 100644 index 00000000..2792a847 --- /dev/null +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/Logger.swift @@ -0,0 +1,101 @@ +// Logger.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 07/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +protocol Logable { + func log(message: String, domain: Logger.Domain, logLevel: Logger.Level) +} + +extension Logable { + static var tag: String { String(describing: self) } +} + +final class Logger { + + enum Level: CaseIterable { + case verbose + case info + case warning + case error + } + + enum Domain: CaseIterable { + case general + case connection + case navigation + case userInterface + case debug + } + + static var domains: [Domain] = [] + private static var loggers: [String: Logable] = [:] + + static func attach(logger: Logable) { + loggers[type(of: logger).tag] = logger + } + + static func log(message: String, domain: Domain, level: Level, tags: [String]? = nil) { + + loggers + .filter { + guard let tags = tags else { return true } + return tags.contains($0.key) + } + .map(\.value) + .forEach { $0.log(message: message, domain: domain, logLevel: level) } + } +} + +extension Logger.Domain { + + var name: String { + switch self { + case .general: + return "General" + case .connection: + return "Connection" + case .navigation: + return "Navigation" + case .userInterface: + return "UI" + case .debug: + return "Debug" + } + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift b/MobileWallet/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift new file mode 100644 index 00000000..53b44333 --- /dev/null +++ b/MobileWallet/TariLib/Wrappers/Utils/Loggers/StatusLoggerManager.swift @@ -0,0 +1,79 @@ +// StatusLoggerManager.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 11/10/2022 + Using Swift 5.0 + Running on macOS 12.6 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import Combine + +final class StatusLoggerManager { + + private var cancellables = Set() + + init() { + setupCallbacks() + } + + private func setupCallbacks() { + + Tari.shared.connectionMonitor.$baseNodeConnection + .sink { Logger.log(message: "Base Node Connection: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$syncStatus + .sink { Logger.log(message: "Sync Status: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$torConnection + .sink { Logger.log(message: "Tor Connection Status: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$networkConnection + .sink { Logger.log(message: "Network Connection Status: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$torBootstrapProgress + .sink { Logger.log(message: "Tor Bootstrap Progress: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + Tari.shared.connectionMonitor.$isTorBootstrapCompleted + .removeDuplicates() + .sink { Logger.log(message: "Is Tor Bootstrap Progress Completed: \($0)", domain: .connection, level: .verbose) } + .store(in: &cancellables) + + } +} diff --git a/MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift b/MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift index 56de0413..177e9941 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/MicroTari.swift @@ -46,14 +46,14 @@ enum MicroTariErrors: Error { struct MicroTari { private static let conversion = 1000000 - public static let ROUNDED_FRACTION_DIGITS = 2 - public static let MAX_FRACTION_DIGITS = 6 + public static let roundedFractionDigits = 2 + public static let maxFractionDigits = 6 private static let defaultFormatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal - formatter.minimumFractionDigits = MicroTari.ROUNDED_FRACTION_DIGITS - formatter.maximumFractionDigits = MicroTari.ROUNDED_FRACTION_DIGITS + formatter.minimumFractionDigits = MicroTari.roundedFractionDigits + formatter.maximumFractionDigits = MicroTari.roundedFractionDigits formatter.negativePrefix = "-" formatter.roundingMode = .down return formatter @@ -62,8 +62,8 @@ struct MicroTari { private static let withOperatorFormatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal - formatter.minimumFractionDigits = MicroTari.ROUNDED_FRACTION_DIGITS - formatter.maximumFractionDigits = MicroTari.MAX_FRACTION_DIGITS + formatter.minimumFractionDigits = MicroTari.roundedFractionDigits + formatter.maximumFractionDigits = MicroTari.maxFractionDigits formatter.positivePrefix = "+ " formatter.negativePrefix = "- " return formatter @@ -72,8 +72,8 @@ struct MicroTari { private static let preciseFormatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal - formatter.minimumFractionDigits = MicroTari.ROUNDED_FRACTION_DIGITS - formatter.maximumFractionDigits = MicroTari.MAX_FRACTION_DIGITS + formatter.minimumFractionDigits = MicroTari.roundedFractionDigits + formatter.maximumFractionDigits = MicroTari.maxFractionDigits formatter.negativePrefix = "- " return formatter }() @@ -82,7 +82,7 @@ struct MicroTari { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.minimumFractionDigits = 0 - formatter.maximumFractionDigits = MicroTari.ROUNDED_FRACTION_DIGITS + formatter.maximumFractionDigits = MicroTari.roundedFractionDigits formatter.negativePrefix = "-" return formatter }() diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift b/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift index e8600f01..66c9125e 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Network/BaseNode.swift @@ -42,7 +42,7 @@ struct BaseNode: Equatable { // MARK: - Subelements - enum Error: Swift.Error { + enum InternalError: Error { case invalidPeerString } @@ -59,7 +59,7 @@ struct BaseNode: Equatable { init(name: String, peer: String) throws { let peerComponents = peer.components(separatedBy: "::") - guard peerComponents.count == 2 else { throw Error.invalidPeerString } + guard peerComponents.count == 2 else { throw InternalError.invalidPeerString } try self.init(name: name, hex: peerComponents[0], address: peerComponents[1]) } @@ -78,7 +78,7 @@ struct BaseNode: Equatable { private func validateData() throws { let regex = try NSRegularExpression(pattern: "[a-z0-9]{64}::\\/onion3\\/[a-z0-9]{56}:[0-9]{2,6}") let range = NSRange(location: 0, length: peer.utf16.count) - guard regex.matches(in: peer, options: [], range: range).count == 1 else { throw Error.invalidPeerString } + guard regex.matches(in: peer, options: [], range: range).count == 1 else { throw InternalError.invalidPeerString } } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift b/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift index 7037bcab..b1f247bf 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Network/NetworkManager.swift @@ -48,7 +48,7 @@ final class NetworkManager { @Published var selectedNetwork: TariNetwork - private static var defaultNetwork: TariNetwork { .dibbler } + private static var defaultNetwork: TariNetwork { .esmeralda } private var cancelables = Set() // MARK: - Initializers diff --git a/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift b/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift index 1d75d1a1..658f56aa 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Network/TariNetwork.swift @@ -90,22 +90,24 @@ extension TariNetwork { extension TariNetwork { - static var all: [TariNetwork] { [dibbler].compactMap { $0 } } + static var all: [TariNetwork] { [esmeralda].compactMap { $0 } } - static var dibbler: Self { + static var esmeralda: Self { makeNetwork( - name: "dibbler", - presentedName: "Dibbler (\(localized("common.recommended")))", + name: "esmeralda", + presentedName: "Esmeralda (\(localized("common.recommended")))", isMainNet: false, rawBaseNodes: [ - "london": "c2eca9cf32261a1343e21ed718e79f25bfc74386e9305350b06f62047f519347::/onion3/6yxqk2ybo43u73ukfhyc42qn25echn4zegjpod2ccxzr2jd5atipwzqd:18141", - "ireland": "42fcde82b44af1de95a505d858cb31a422c56c4ac4747fbf3da47d648d4fc346::/onion3/2l3e7ysmihc23zybapdrsbcfg6omtjtfkvwj65dstnfxkwtai2fawtyd:18141", - "ncal": "50e6aa8f6c50f1b9d9b3d438dfd2a29cfe1f3e3a650bd9e6b1e10f96b6c38f4d::/onion3/7s6y3cz5bnewlj5ypm7sekhgvqjyrq4bpaj5dyvvo7vxydj7hsmyf5ad:18141", - "nvir": "36a9df45e1423b5315ffa7a91521924210c8e1d1537ad0968450f20f21e5200d::/onion3/v24qfheti2rztlwzgk6v4kdbes3ra7mo3i2fobacqkbfrk656e3uvnid:18141", - "oregon": "be128d570e8ec7b15c101ee1a56d6c56dd7d109199f0bd02f182b71142b8675f::/onion3/ha422qsy743ayblgolui5pg226u42wfcklhc5p7nbhiytlsp4ir2syqd:18141", - "seoul": "3e0321c0928ca559ab3c0a396272dfaea705efce88440611a38ff3898b097217::/onion3/sl5ledjoaisst6d4fh7kde746dwweuge4m4mf5nkzdhmy57uwgtb7qqd:18141" + "london": "083ff333ad7e0e9f3678b67378ec339074474342a6357de64a76bdf15e4c955b::/onion3/ldgdytcrwzfbmbpz3dmyi6yzqzqbeamitpb2saxzxmp52qywlmsg4vyd:18141", + "ireland": "747d45b8416ffe605d17958d36747eab7d3f5fc3d671e1b1b4884b2a44b98365::/onion3/n35n5fikcqqhhhjcdzpqul5v74mu7k6nikpafmlnwgiapphiw45tmryd:18141", + "ncal": "ea420ae2948739bc35907b8ab5a2d41526ccef22ec92f8f8e2bb398500bf435a::/onion3/uybnlnzve4j4w2lj5bdoe2uurwsbjm73ck2cotlnknhu2l7msn26oeyd:18141", + "nvir": "f688c69f2397dc0d4ad18168cd6ad13f93241a665acf19ab7f358fd661ac3d1c::/onion3/qejny5yprzidxt4rhstjmhsyfmeq4yb4r6tnn3pqowjr7e7roxcpxsqd:18141", + "oregon": "8648575c606269b032f43cd0d54728628ddb911e636bd65ea36e867a5ffd3643::/onion3/5d2owx6uoqcsoapprattb4fmektm3rcpfyzmmwmf64dsu55mhcqef2yd:18141", + "seoul": "78b2c0bda70fd12a9987757ffc2851e197080af804353e8e025d28c785b6b447::/onion3/ysj76foyp7qkl7d5x63hyocmp5ydwcgkb25oalo23kj2vvx7zjvofqad:18141", + "area7": "ced769f66b4398ea62eb9f74a08b5ebfdc1a51554a695c0aff4b949fea875b61::/onion3/m4koteatwthmozsbg54y6c4io26d4md6ub5l3rnco4s7a4qu2xkvonyd:18141", + "area8": "40717ea5146cf6183c07469d188792b12a57b9da2e5af5bc50df270ff789257f::/onion3/qhmrwr2h3fnszwc4udhlgfpealm7mvw64enqghullrarc633fzmd6zqd:18141" ], - faucetURL: URL(string: "https://dibbler-faucet.tari.com") + faucetURL: URL(string: "https://esmeralda-faucet.tari.com") ) } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift b/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift index c2537896..2ca6dffa 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Settings/TariSettings.swift @@ -53,8 +53,6 @@ struct TariSettings { let walletSettings = WalletSettingsManager() - let discoveryTimeoutSec: UInt64 = 20 - let safMessageDurationSec: UInt64 = 10800 let deeplinkURI = "tari" let iCloudContainerIdentifier = "iCloud.com.tari.wallet" @@ -67,8 +65,8 @@ struct TariSettings { let storeUrl = "https://store.tarilabs.com/" let bugReportEmail = "bug_reports@tari.com" let tariLabsUniversityUrl = "https://tlu.tarilabs.com/" - let blockExplorerUrl = "https://explore.tari.com/" - let blockExplorerKernelUrl = "https://explore.tari.com/kernel/" + let blockExplorerUrl = "https://explore-esme.tari.com/" + let blockExplorerKernelUrl = "https://explore-esme.tari.com/kernel/" var pushServerApiKey: String? var sentryPublicDSN: String? @@ -122,11 +120,11 @@ struct TariSettings { } private init() { - TariLogger.info("Init settings...") - TariLogger.warn("Environment: \(environment)") + Logger.log(message: "Init settings...", domain: .general, level: .info) + Logger.log(message: "Environment: \(environment)", domain: .general, level: .info) guard let envPath = Bundle.main.path(forResource: "env", ofType: "json") else { - TariLogger.error("Could not find envrionment file") + Logger.log(message: "Could not find envrionment file", domain: .general, level: .error) return } @@ -138,18 +136,18 @@ struct TariSettings { if let pushServerApiKey = jsonResult["pushServerApiKey"] as? String, !pushServerApiKey.isEmpty { self.pushServerApiKey = pushServerApiKey } else { - TariLogger.warn("pushServerApiKey not set in env.json. Sending push notifications will be disabled.") + Logger.log(message: "pushServerApiKey not set in env.json. Sending push notifications will be disabled.", domain: .general, level: .warning) } if let sentryPublicDSN = jsonResult["sentryPublicDSN"] as? String, !sentryPublicDSN.isEmpty { self.sentryPublicDSN = sentryPublicDSN } else { - TariLogger.warn("sentryPublicDSN not set in env.json. Crash reporting will not work.") + Logger.log(message: "sentryPublicDSN not set in env.json. Crash reporting will not work.", domain: .general, level: .warning) } if let giphyApiKey = jsonResult["giphyApiKey"] as? String, !giphyApiKey.isEmpty { self.giphyApiKey = giphyApiKey } else { - TariLogger.warn("giphyApiKey not set in env.json. Appending gifs to transaction notes will not work.") + Logger.log(message: "giphyApiKey not set in env.json. Appending gifs to transaction notes will not work.", domain: .general, level: .warning) } if let appleTeamID = jsonResult["appleTeamID"] as? String, !appleTeamID.isEmpty { @@ -180,7 +178,7 @@ struct TariSettings { } } } catch { - TariLogger.error("Could not load env vars", error: error) + Logger.log(message: "Could not load env vars: \(error)", domain: .general, level: .error) } } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift b/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift index 3949fa84..3646df4d 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettings.swift @@ -52,7 +52,7 @@ struct WalletSettings: Codable, Equatable { } let networkName: String - let configationState: WalletConfigurationState + let configurationState: WalletConfigurationState let isCloudBackupEnabled: Bool let hasVerifiedSeedPhrase: Bool let yat: String? @@ -61,8 +61,8 @@ struct WalletSettings: Codable, Equatable { } extension WalletSettings { - func update(configationState: WalletConfigurationState) -> Self { Self(networkName: networkName, configationState: configationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } - func update(isCloudBackupEnabled: Bool) -> Self { Self(networkName: networkName, configationState: configationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } - func update(hasVerifiedSeedPhrase: Bool) -> Self { Self(networkName: networkName, configationState: configationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } - func update(yat: String?) -> Self { Self(networkName: networkName, configationState: configationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } + func update(configurationState: WalletConfigurationState) -> Self { Self(networkName: networkName, configurationState: configurationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } + func update(isCloudBackupEnabled: Bool) -> Self { Self(networkName: networkName, configurationState: configurationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } + func update(hasVerifiedSeedPhrase: Bool) -> Self { Self(networkName: networkName, configurationState: configurationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } + func update(yat: String?) -> Self { Self(networkName: networkName, configurationState: configurationState, isCloudBackupEnabled: isCloudBackupEnabled, hasVerifiedSeedPhrase: hasVerifiedSeedPhrase, yat: yat) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift b/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift index 708885af..7bab7efb 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/Settings/Wallet/WalletSettingsManager.swift @@ -43,12 +43,12 @@ final class WalletSettingsManager { private var settings: WalletSettings { guard let networkName = GroupUserDefaults.selectedNetworkName else { - return WalletSettings(networkName: "", configationState: .notConfigured, isCloudBackupEnabled: false, hasVerifiedSeedPhrase: false, yat: nil) + return WalletSettings(networkName: "", configurationState: .notConfigured, isCloudBackupEnabled: false, hasVerifiedSeedPhrase: false, yat: nil) } guard let existingSettings = GroupUserDefaults.walletSettings?.first(where: { $0.networkName == networkName }) else { var settings = GroupUserDefaults.walletSettings ?? [] - let newSettings = WalletSettings(networkName: networkName, configationState: .notConfigured, isCloudBackupEnabled: false, hasVerifiedSeedPhrase: false, yat: nil) + let newSettings = WalletSettings(networkName: networkName, configurationState: .notConfigured, isCloudBackupEnabled: false, hasVerifiedSeedPhrase: false, yat: nil) settings.append(newSettings) GroupUserDefaults.walletSettings = settings return newSettings @@ -57,9 +57,9 @@ final class WalletSettingsManager { return existingSettings } - var configationState: WalletSettings.WalletConfigurationState { - get { settings.configationState } - set { update(settings: settings.update(configationState: newValue)) } + var configurationState: WalletSettings.WalletConfigurationState { + get { settings.configurationState } + set { update(settings: settings.update(configurationState: newValue)) } } var isCloudBackupEnabled: Bool { diff --git a/MobileWallet/TariLib/Wrappers/Utils/StorageCleanup.swift b/MobileWallet/TariLib/Wrappers/Utils/StorageCleanup.swift index 9ea30e53..b34f4284 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/StorageCleanup.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/StorageCleanup.swift @@ -49,7 +49,7 @@ private func totalBytes(files: [URL]) -> UInt64 { total = total + fileSize } } catch { - TariLogger.error("Failed to get log file size", error: error) + Logger.log(message: "Failed to get log file size: \(error.localizedDescription)", domain: .general, level: .error) } } @@ -74,7 +74,7 @@ private func bugReportZipFilesCleanup() { } } } catch { - TariLogger.error("Cannot cleanup the old zip files from bug reports files", error: error) + Logger.log(message: "Cannot cleanup the old zip files from bug reports files: \(error.localizedDescription)", domain: .general, level: .error) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/TariEventBus.swift b/MobileWallet/TariLib/Wrappers/Utils/TariEventBus.swift deleted file mode 100644 index cddbb2d9..00000000 --- a/MobileWallet/TariLib/Wrappers/Utils/TariEventBus.swift +++ /dev/null @@ -1,216 +0,0 @@ -// TariEventBus.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/01/22 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation -import Combine - -public enum TariEventTypes: String { - // Wallet autobackup - case requiresBackup = "tari-event-requires-backup" - - // Wallet callbacks - case receivedTx = "tari-event-received-tx" - case receievedTxReply = "tari-event-receieved-tx-reply" - case receivedFinalizedTx = "tari-event-received-finalized-tx" - case txBroadcast = "tari-event-tx-broadcast" - case txMined = "tari-event-tx-mined" - case txMinedUnconfirmed = "tari-event-tx-mined-unconfirmed" - case transactionSendResult = "tari-event-transaction-send-result" - case txCancellation = "tari-event-tx-cancellation" - case baseNodeSyncStarted = "tari-event-base-node-sync-started" - case baseNodeSyncComplete = "tari-event-base-node-sync-complete" - case txValidationSuccessful = "tari-event-tx-validation-successful" - - // Common UI updates - case txListUpdate = "tari-event-tx-list-update" - case balanceUpdate = "tari-event-balance-update" - - // Tor statuses - case torPortsOpened = "tari-event-tor-ports-opened" - case torConnectionProgress = "tari-event-tor-connection-progress" - case torConnected = "tari-event-tor-connected" - case torConnectionFailed = "tari-event-tor-connection-failed" - - // Connection status - - case connectionStatusChanged = "tari-event-connection-status" - - // wallet - @available(*, deprecated, message: "Please use TariLib.shared.walletStatePublisher instead") - case walletStateChanged = "tari-event-wallet-state-changed" - - // connection monitor - case connectionMonitorStatusChanged = "connection-monitor-status-changed" - - // restore wallet from seed words - case restoreWalletStatusUpdate = "restore-wallet-status-update" -} - -private let IDENTIFIER = "com.tari.eventbus" - -open class TariEventBus { - static let shared = TariEventBus() - static let queue = DispatchQueue(label: IDENTIFIER, attributes: []) - - struct NamedObserver { - let observer: NSObjectProtocol - let eventType: TariEventTypes - } - - var cache = [UInt: [NamedObserver]]() - - // MARK: Publish - - open class func postToMainThread( - _ eventType: TariEventTypes, - sender: Any? = nil - ) { - DispatchQueue.main.async { - NotificationCenter.default.post( - name: Notification.Name(rawValue: eventType.rawValue), - object: sender - ) - } - } - - // MARK: Subscribe - - @discardableResult - open class func on( - _ target: AnyObject, - eventType: TariEventTypes, - sender: Any? = nil, - queue: OperationQueue?, - handler: @escaping ((Notification?) -> Void) - ) -> NSObjectProtocol { - let id = UInt(bitPattern: ObjectIdentifier(target)) - let observer = NotificationCenter.default.addObserver( - forName: NSNotification.Name(rawValue: eventType.rawValue), - object: sender, - queue: queue, - using: handler - ) - let namedObserver = NamedObserver(observer: observer, eventType: eventType) - - TariEventBus.queue.sync { - if let namedObservers = TariEventBus.shared.cache[id] { - TariEventBus.shared.cache[id] = namedObservers + [namedObserver] - } else { - TariEventBus.shared.cache[id] = [namedObserver] - } - } - - return observer - } - - @discardableResult - open class func onMainThread( - _ target: AnyObject, - eventType: TariEventTypes, - sender: Any? = nil, - handler: @escaping ((Notification?) -> Void) - ) -> NSObjectProtocol { - return TariEventBus.on( - target, - eventType: eventType, - sender: sender, - queue: OperationQueue.main, - handler: handler - ) - } - - @discardableResult - open class func onBackgroundThread( - _ target: AnyObject, - eventType: TariEventTypes, - sender: Any? = nil, - handler: @escaping ((Notification?) -> Void) - ) -> NSObjectProtocol { - return TariEventBus.on( - target, - eventType: eventType, - sender: sender, - queue: OperationQueue(), - handler: handler - ) - } - - static func events(forType type: TariEventTypes) -> AnyPublisher { - NotificationCenter.default - .publisher(for: Notification.Name(type.rawValue)) - .eraseToAnyPublisher() - } - - // MARK: Unregister - - open class func unregister(_ target: AnyObject) { - let id = UInt(bitPattern: ObjectIdentifier(target)) - let center = NotificationCenter.default - - TariEventBus.queue.sync { - if let namedObservers = TariEventBus.shared.cache.removeValue(forKey: id) { - for namedObserver in namedObservers { - center.removeObserver(namedObserver.observer) - } - } - } - } - - open class func unregister(_ target: AnyObject, eventType: TariEventTypes) { - let id = UInt(bitPattern: ObjectIdentifier(target)) - let center = NotificationCenter.default - - TariEventBus.queue.sync { - if let namedObservers = TariEventBus.shared.cache[id] { - TariEventBus.shared.cache[id] = namedObservers.filter({ - (namedObserver: NamedObserver) -> Bool in - if namedObserver.eventType == eventType { - center.removeObserver(namedObserver.observer) - return false - } else { - return true - } - } - ) - } - } - } - -} diff --git a/MobileWallet/TariLib/Wrappers/Utils/TariLogger.swift b/MobileWallet/TariLib/Wrappers/Utils/TariLogger.swift deleted file mode 100644 index ae42bc5d..00000000 --- a/MobileWallet/TariLib/Wrappers/Utils/TariLogger.swift +++ /dev/null @@ -1,124 +0,0 @@ -// TariLogger.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/03/21 - Using Swift 5.0 - Running on macOS 10.15 - - Used for logging from Swift code. Protocol logging happens in the Tari library. - - Adapted from https://github.com/LN-Zap/zap-iOS/blob/master/Logger/Logger.swift -*/ - -import Foundation - -// Called TariLogger because it should be included when TariLib is moved into its own pod -public class TariLogger { - /// Used for crash reporting - public static var breadcrumbCallback: ((String, Level) -> Void?)? - - public enum Level: String { - case info = "INFO" - case verbose = "DEBUG" - case warning = "WARN" - case error = "ERROR" - case tor = "ONION" - - var emoji: String { - switch self { - case .info: - return "โ„น๏ธ" - case .verbose: - return "๐Ÿคซ" - case .warning: - return "โš ๏ธ" - case .error: - return "โŒ" - case .tor: - return "๐Ÿง…" - } - } - } - - private static let dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH:mm:ss.SSS" - return dateFormatter - }() - - private static var time: String { - return dateFormatter.string(from: Date()) - } - - public static func info(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) { - log(level: .info, message: message, file: file, function: function, line: line) - } - - public static func verbose(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) { - log(level: .verbose, message: message, file: file, function: function, line: line) - } - - public static func warn(_ message: Any, error: Error? = nil, file: String = #file, function: String = #function, line: Int = #line) { - log( - level: .warning, - message: error != nil ? "\(message) โ—\(String(describing: error!.localizedDescription))โ—" : message, - file: file, - function: function, - line: line - ) - } - - public static func error(_ message: Any, error: Error? = nil, file: String = #file, function: String = #function, line: Int = #line) { - log( - level: .error, - message: error != nil ? "\(message) โ—\(String(describing: error!.localizedDescription))โ—" : message, - file: file, - function: function, - line: line - ) - } - - public static func tor(_ message: Any, error: Error? = nil, file: String = #file, function: String = #function, line: Int = #line) { - log( - level: .tor, - message: error != nil ? "\(message) โ—\(String(describing: error!.localizedDescription))โ—" : message, - file: file, - function: function, - line: line - ) - } - - private static func sourceFileName(filePath: String) -> String { - let components = filePath.components(separatedBy: "/") - return components.last?.components(separatedBy: ".").first ?? "" - } - - private static func log(level: Level = .info, message: Any, file: String = #file, function: String = #function, line: Int = #line) { - let logMessage = "\(message) - (\(sourceFileName(filePath: file)).\(function):\(line))".trimmingCharacters(in: .whitespacesAndNewlines) - - TariLogger.logToFile("SWIFT (\(level.rawValue)): \(logMessage)") - - if let breadcrumb = breadcrumbCallback { - breadcrumb(logMessage, level) - } - - // xcode debugger gets flooded without this check - if let msg = message as? String { - guard !msg.contains("[Tor") else { - return - } - } - - print("\(time) \(level.emoji) \(logMessage)") - } - - private static func logToFile(_ message: String) { - - var errorCode: Int32 = -1 - - withUnsafeMutablePointer(to: &errorCode) { errorCodePointer in - message.withCString { log_debug_message($0, errorCodePointer) } - } - } -} diff --git a/MobileWallet/TariLib/Wrappers/Utils/UTXO.swift b/MobileWallet/TariLib/Wrappers/Utils/UTXO.swift index f9acafe6..9fbae348 100644 --- a/MobileWallet/TariLib/Wrappers/Utils/UTXO.swift +++ b/MobileWallet/TariLib/Wrappers/Utils/UTXO.swift @@ -38,8 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation - struct UTXO: Codable { let privateKeyHex: String let value: UInt64 @@ -49,8 +47,4 @@ struct UTXO: Codable { let uValue: Data let vValue: Data let senderOffsetPublicKeyHex: String - - func getPrivateKey() throws -> PrivateKey { try PrivateKey(hex: privateKeyHex) } - func getSourcePublicKey() throws -> PublicKey { try PublicKey(hex: sourcePublicKeyHex) } - func makeSenderOffsetPublicKey() throws -> PublicKey { try PublicKey(hex: senderOffsetPublicKeyHex) } } diff --git a/MobileWallet/TariLib/Wrappers/Utils/WalletConnectivityManager.swift b/MobileWallet/TariLib/Wrappers/Utils/WalletConnectivityManager.swift deleted file mode 100644 index 871730e7..00000000 --- a/MobileWallet/TariLib/Wrappers/Utils/WalletConnectivityManager.swift +++ /dev/null @@ -1,119 +0,0 @@ -// WalletConnectivityManager.swift - -/* - Package MobileWallet - Created by Adrian Truszczynski on 09/11/2021 - Using Swift 5.0 - Running on macOS 12.0 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Combine - -final class WalletConnectivityManager { - - private static var cancellables = Set() - - static func connectWithTor(completion: @escaping () -> Void) { - // Handle if tor ports opened later - TariEventBus.onMainThread(self, eventType: .torPortsOpened) { _ in - TariEventBus.unregister(self, eventType: .torPortsOpened) - completion() - } - if TariLib.shared.torPortsOpened { - TariEventBus.unregister(self, eventType: .torPortsOpened) - completion() - } - } - - static func startWalletIfNeeded() { - guard TariLib.shared.walletState == .notReady else { return } - TariLib.shared.startWallet(seedWords: nil) - } - - static func waitForWallet(result: @escaping (Result) -> Void) { - - var cancel: AnyCancellable? - - cancel = TariLib.shared.walletStatePublisher - .receive(on: RunLoop.main) - .sink { walletState in - switch walletState { - case .started: - cancel?.cancel() - result(.success(Void())) - case let .startFailed(error): - cancel?.cancel() - result(.failure(error)) - case .notReady, .starting: - break - } - } - - cancel?.store(in: &cancellables) - } - - static func startWallet(result: @escaping (Result) -> Void) { - - let dispatchGroup = DispatchGroup() - var startWalletResult: Result? - - dispatchGroup.enter() - connectWithTor { - dispatchGroup.leave() - } - - dispatchGroup.enter() - waitForWallet { - startWalletResult = $0 - dispatchGroup.leave() - } - - dispatchGroup.notify(queue: .main) { - - guard let startWalletResult = startWalletResult else { - result(.failure(.unknown)) - return - } - - switch startWalletResult { - case .success: - result(.success(Void())) - case let .failure(error): - result(.failure(error)) - } - } - - startWalletIfNeeded() - } -} diff --git a/MobileWallet/TariLib/Wrappers/Wallet.swift b/MobileWallet/TariLib/Wrappers/Wallet.swift deleted file mode 100644 index 1bb922c0..00000000 --- a/MobileWallet/TariLib/Wrappers/Wallet.swift +++ /dev/null @@ -1,1057 +0,0 @@ -// Wallet.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2019/11/15 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -enum WalletErrors: Error { - case generic(_ errorCode: Int32) - case insufficientFunds(microTariSpendable: MicroTari) - case addUpdateContact - case removeContact - case addOwnContact - case invalidPublicKeyHex - case generateTestData - case generateTestReceiveTx - case sendingTx - case testTxBroadcast - case testTxMined - case testSendCompleteTx - case completedTxById - case cancelledTxById - case walletNotInitialized - case invalidSignatureAndNonceString - case cancelNonPendingTx - case txToCancel - case notEnoughFunds - case fundsPending -} - -enum TrasactionSendStatus: UInt32 { - case queued - case directSendSafSend - case directSend - case safSend - case invalid - - var isDirectSend: Bool { - switch self { - case .directSend, .directSendSafSend: - return true - case .queued, .safSend, .invalid: - return false - } - } - - var isSafSend: Bool { - switch self { - case .directSendSafSend, .safSend: - return true - case .queued, .directSend, .invalid: - return false - } - } - - var isQueued: Bool { - switch self { - case .queued: - return true - case .directSendSafSend, .directSend, .safSend, .invalid: - return false - } - } - - var isSuccess: Bool { isDirectSend || isSafSend || !isQueued } -} - -private enum BaseNodeValidationType: String { - case txo - case tx -} - -struct TransactionResult { - let id: UInt64 - let status: TrasactionSendStatus -} - -enum RestoreWalletStatus { - case unknown - case connectingToBaseNode - case connectedToBaseNode - case connectionFailed(attempt: UInt64, maxAttempts: UInt64) - case progress(restoredUTXOs: UInt64, totalNumberOfUTXOs: UInt64) - case completed - case scanningRoundFailed(attempt: UInt64, maxAttempts: UInt64) - case recoveryFailed - - init(status: UInt8, firstValue: UInt64, secondValue: UInt64) { - - switch status { - case 0: - self = .connectingToBaseNode - case 1: - self = .connectedToBaseNode - case 2: - self = .connectionFailed(attempt: firstValue, maxAttempts: secondValue) - case 3: - self = .progress(restoredUTXOs: firstValue, totalNumberOfUTXOs: secondValue) - case 4: - self = .completed - case 5: - self = .scanningRoundFailed(attempt: firstValue, maxAttempts: secondValue) - case 6: - self = .recoveryFailed - default: - self = .unknown - } - } -} - -struct WalletBalance: Equatable { - var available: UInt64 - var incoming: UInt64 - var outgoing: UInt64 - var timeLocked: UInt64 -} - -enum BaseNodeConnectivityStatus: UInt64 { - case connecting - case online - case offline -} - -final class Wallet { - - private(set) var pointer: OpaquePointer - - var dbPath: String - var dbName: String - var logPath: String - - private var requiredConfirmationCount: UInt64? - - static let defaultFeePerGram = MicroTari(10) - static let defaultKernelCount = UInt64(1) - static let defaultOutputCount = UInt64(2) - - private static var baseNodeValidationStatusMap: [BaseNodeValidationType: (UInt64, Bool?)] = [:] - - var contacts: (Contacts?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - Contacts(contactsPointer: wallet_get_contacts(pointer, error)) - }) - guard errorCode == 0 else { - return (nil, WalletErrors.generic(errorCode)) - } - return (result, nil) - } - - var seedWords: ([String]?, Error?) { - do { - let seedWords = try SeedWords(walletPointer: pointer) - let result = try seedWords.allElements() - return (result, nil) - } catch { - return (nil, error) - } - } - - private var walletBalance = WalletBalance(available: 0, incoming: 0, outgoing: 0, timeLocked: 0) - - var publicKey: (PublicKey?, Error?) { - var errorCode: Int32 = -1 - let result = PublicKey(pointer: withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_public_key(pointer, error)})) - guard errorCode == 0 else { - return (nil, WalletErrors.generic(errorCode)) - } - - return (result, nil) - } - - private static func checkValidationResult(type: BaseNodeValidationType, responseId: UInt64, isSuccess: Bool) { - - guard let currentStatus = baseNodeValidationStatusMap[type] else { - TariLogger.info( - "\(type.rawValue) validation [\(responseId)] complete. Success: \(isSuccess)." - + " Current status is null, means we're not expecting a callback. Ignoring." - ) - return - } - - if currentStatus.0 != responseId { - TariLogger.info( - "\(type.rawValue) validation [\(responseId)] complete. Success: \(isSuccess)." - + " Request id [\(currentStatus.0)] mismatch. Ignoring." - ) - return - } - - TariLogger.info("\(type.rawValue) validation [\(responseId)] complete. Success: \(isSuccess).") - - baseNodeValidationStatusMap[type] = (currentStatus.0, isSuccess) - Wallet.checkBaseNodeSyncCompletion() - } - - static func checkBaseNodeSyncCompletion() { - guard !handleSyncStatus(isSuccess: false) else { - try? TariLib.shared.setupBasenode() - return - } - guard !handleSyncStatus(isSuccess: .none) else { return } - handleSyncStatus(isSuccess: true) - } - - @discardableResult private static func handleSyncStatus(isSuccess: Bool?) -> Bool { - let result = baseNodeValidationStatusMap.contains { $0.value.1 == isSuccess } - guard result, isSuccess != nil else { return result } - baseNodeValidationStatusMap.removeAll() - TariEventBus.postToMainThread(.baseNodeSyncComplete, sender: ["success": isSuccess]) - return result - } - - init(commsConfig: CommsConfig, loggingFilePath: String, seedWords: SeedWords?, networkName: String) throws { - let loggingFilePathPointer = UnsafeMutablePointer(mutating: (loggingFilePath as NSString).utf8String)! - - let receivedTxCallback: (@convention(c) (OpaquePointer?) -> Void)? = { - valuePointer in - let pendingInbound = PendingInboundTx(pendingInboundTxPointer: valuePointer!) - TariEventBus.postToMainThread(.receivedTx, sender: pendingInbound) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Receive transaction lib callback") - } - - let receivedTxReplyCallback: (@convention(c) (OpaquePointer?) -> Void)? = { - valuePointer in - let completed = CompletedTx(completedTxPointer: valuePointer!) - TariEventBus.postToMainThread(.receievedTxReply, sender: completed) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Receive transaction reply lib callback") - } - - let receivedFinalizedTxCallback: (@convention(c) (OpaquePointer?) -> Void)? = { - valuePointer in - let completed = CompletedTx(completedTxPointer: valuePointer!) - TariEventBus.postToMainThread(.receivedFinalizedTx, sender: completed) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Receive finalized transaction lib callback") - } - - let txBroadcastCallback: (@convention(c) (OpaquePointer?) -> Void)? = { - valuePointer in - let completed = CompletedTx(completedTxPointer: valuePointer!) - TariEventBus.postToMainThread(.txBroadcast, sender: completed) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Transaction broadcast lib callback") - } - - let txMinedCallback: (@convention(c) (OpaquePointer?) -> Void)? = { - valuePointer in - let completed = CompletedTx(completedTxPointer: valuePointer!) - TariEventBus.postToMainThread(.txMined, sender: completed) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Transaction mined lib callback") - } - - let txMinedUnconfirmedCallback: (@convention(c) (OpaquePointer?, UInt64) -> Void)? = { - valuePointer, confirmationCount in - let completed = CompletedTx(completedTxPointer: valuePointer!) - TariEventBus.postToMainThread(.txMinedUnconfirmed, sender: completed) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Transaction mined unconfirmed lib callback - \(confirmationCount) confirmations") - } - - let fauxTransactionConfirmed: (@convention(c) (OpaquePointer?) -> Void)? = { _ in - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - } - - let fauxTransactionUncorfirmed: (@convention(c) (OpaquePointer?, UInt64) -> Void)? = { _, _ in - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - } - - let transactionSendResultCallback: @convention(c) (UInt64, OpaquePointer?) -> Void = { transactionID, transactionSendStatusPointer in - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let result = transaction_send_status_decode(transactionSendStatusPointer, errorCodePointer) - guard errorCode == 0, let status = TrasactionSendStatus(rawValue: result) else { return } - TariEventBus.postToMainThread(.transactionSendResult, sender: TransactionResult(id: transactionID, status: status)) - - let message = "Store and forward lib callback. txID=\(transactionID)" - - guard status.isSuccess else { - TariLogger.error("\(message) FAILURE") - return - } - - TariLogger.verbose("\(message) SUCCESS") - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.balanceUpdate) - TariEventBus.postToMainThread(.requiresBackup) - } - - let txCancellationCallback: (@convention(c) (OpaquePointer?, UInt64) -> Void)? = { valuePointer, rejectonReason in - let cancelledTxId = CompletedTx(completedTxPointer: valuePointer!).id - TariEventBus.postToMainThread(.txListUpdate) - TariEventBus.postToMainThread(.txCancellation) - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Transaction cancelled callback. txID=\(cancelledTxId) โœ…") - } - - let txoValidationCallback: (@convention(c) (UInt64, Bool) -> Void) = { responseId, isSuccess in - DispatchQueue.global().async { - Wallet.checkValidationResult(type: .txo, responseId: responseId, isSuccess: isSuccess) - } - } - - let txValidationCompleteCallback: (@convention(c) (UInt64, Bool) -> Void)? = { responseId, isSuccess in - Wallet.checkValidationResult(type: .tx, responseId: responseId, isSuccess: isSuccess) - guard isSuccess else { return } - TariEventBus.postToMainThread(.txValidationSuccessful) - } - - let storedMessagesReceivedCallback: (@convention(c) () -> Void)? = { - TariLogger.verbose("Stored messages received โœ…") - } - - let contactsLivenessDataUpdatedCallback: (@convention(c) (OpaquePointer?) -> Void) = { _ in - } - - let balanceUpdatedCallback: (@convention(c) (OpaquePointer?) -> Void)? = { valuePointer in - //Note context for this is unavailable withing the callback but we still need to free the object passed in form the library - let _ = Balance(pointer: valuePointer!) - TariEventBus.postToMainThread(.balanceUpdate) - TariLogger.verbose("Balance updated callback") - } - - let callbackConectivityStatus: (@convention(c) (UInt64) -> Void)? = { status in - guard let status = BaseNodeConnectivityStatus(rawValue: status) else { return } - TariEventBus.postToMainThread(.connectionStatusChanged, sender: status) - } - - dbPath = commsConfig.dbPath - dbName = commsConfig.dbName - logPath = loggingFilePath - - func createWallet(passphrase: String?, seedWords: SeedWords?, networkName: String) -> (result: OpaquePointer?, error: WalletError?) { - - var errorCode: Int32 = -1 - var isRecoveryInProgress = false - - let result = withUnsafeMutablePointer(to: &isRecoveryInProgress) { isRecoveryInProgressPointer in - withUnsafeMutablePointer(to: &errorCode, { error in - wallet_create( - commsConfig.pointer, - loggingFilePathPointer, - 2, // number of rolling log files - 10 * 1024 * 1024, // rolling log file max size in bytes - passphrase, - seedWords?.pointer, - networkName, - receivedTxCallback, - receivedTxReplyCallback, - receivedFinalizedTxCallback, - txBroadcastCallback, - txMinedCallback, - txMinedUnconfirmedCallback, - fauxTransactionConfirmed, - fauxTransactionUncorfirmed, - transactionSendResultCallback, - txCancellationCallback, - txoValidationCallback, - contactsLivenessDataUpdatedCallback, - balanceUpdatedCallback, - txValidationCompleteCallback, - storedMessagesReceivedCallback, - callbackConectivityStatus, - isRecoveryInProgressPointer, - error - ) - }) - } - - var error: WalletError? - - if errorCode > 0 { - error = WalletError(code: errorCode) - } - - return (result, error) - } - - func handleInitializationFlow(passphrase: String?, networkName: String) throws -> (pointer: OpaquePointer, encryptionEnabled: Bool) { - - let createWalletResponse = createWallet(passphrase: passphrase, seedWords: seedWords, networkName: networkName) - - switch createWalletResponse { - case (_, .some(.invalidPassphrase)) where passphrase != nil: - return try handleInitializationFlow(passphrase: nil, networkName: networkName) - case (_, .some(let error)): - throw error - case (.some(let result), _): - return (result, passphrase != nil) - default: - fatalError() - } - } - - let result = try handleInitializationFlow(passphrase: Self.passphrase(), networkName: networkName) - - pointer = result.pointer - - guard !result.encryptionEnabled else { return } - try enableEncryption() - } - - func removeContact(_ contact: Contact) throws { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_remove_contact(pointer, contact.pointer, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - if !result { - throw WalletErrors.removeContact - } - } - - func addUpdateContact(alias: String, publicKeyHex: String) throws { - let publicKey = try PublicKey(hex: publicKeyHex) - try addUpdateContact(alias: alias, publicKey: publicKey) - } - - func addUpdateContact(alias: String, publicKey: PublicKey) throws { - let (currentWalletPublicKey, publicKeyError) = self.publicKey - if publicKeyError != nil { - throw publicKeyError! - } - - let (currentWalletPublicKeyHex, currentWalletPublicKeyHexError) = currentWalletPublicKey!.hex - if currentWalletPublicKeyHexError != nil { - throw currentWalletPublicKeyHexError! - } - - if publicKey.hex.0 == currentWalletPublicKeyHex { - throw WalletErrors.addOwnContact - } - - let newContact = try Contact(alias: alias, publicKey: publicKey) - var errorCode: Int32 = -1 - let contactAdded = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_upsert_contact(pointer, newContact.pointer, error)}) - - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - if !contactAdded { - throw WalletErrors.addUpdateContact - } - - TariEventBus.postToMainThread(.txListUpdate) - } - - func estimateTxFee(amount: MicroTari, feePerGram: MicroTari, kernelCount: UInt64, outputCount: UInt64) throws -> MicroTari { - var errorCode: Int32 = -1 - - let fee = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_fee_estimate(pointer, amount.rawValue, feePerGram.rawValue, kernelCount, outputCount, error)}) - guard errorCode == 0 else { - if errorCode == 101 { - throw WalletErrors.notEnoughFunds - } else if errorCode == 115 { - throw WalletErrors.fundsPending - } - throw WalletErrors.generic(errorCode) - } - - return MicroTari(fee) - } - - func sendTx(destination: PublicKey, amount: MicroTari, feePerGram: MicroTari, message: String, isOneSidedPayment: Bool) throws -> UInt64 { - var fee = MicroTari(0) - do { - fee = try estimateTxFee( - amount: amount, - feePerGram: feePerGram, - kernelCount: Wallet.defaultKernelCount, - outputCount: Wallet.defaultOutputCount - ) - } catch { - throw error - } - let total = fee.rawValue + amount.rawValue - let availableBalance = self.walletBalance.available - - if total > availableBalance { - throw WalletErrors.insufficientFunds(microTariSpendable: MicroTari(availableBalance)) - } - - let messagePointer = UnsafeMutablePointer(mutating: (message as NSString).utf8String) - var errorCode: Int32 = -1 - - let txId = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_send_transaction( - pointer, - destination.pointer, - amount.rawValue, - feePerGram.rawValue, - messagePointer, - isOneSidedPayment, - error - ) - }) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - if txId == 0 { - throw WalletErrors.sendingTx - } - - return txId - } - - func findPendingOutboundTxBy(id: UInt64) throws -> PendingOutboundTx? { - var errorCode: Int32 = -1 - let pendingOutboundTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_pending_outbound_transaction_by_id(pointer, id, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - if let txPointer = pendingOutboundTxPointer { - return PendingOutboundTx(pendingOutboundTxPointer: txPointer) - } - return nil - } - - func findPendingInboundTxBy(id: UInt64) throws -> PendingInboundTx? { - var errorCode: Int32 = -1 - let pendingInboundTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_pending_inbound_transaction_by_id(pointer, id, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - if let txPointer = pendingInboundTxPointer { - return PendingInboundTx(pendingInboundTxPointer: txPointer) - } - return nil - } - - func findCompletedTxBy(id: UInt64) throws -> CompletedTx { - var errorCode: Int32 = -1 - let completedTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_completed_transaction_by_id(pointer, id, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - guard completedTxPointer != nil else { - throw WalletErrors.completedTxById - } - - return CompletedTx(completedTxPointer: completedTxPointer!) - } - - func findCancelledTxBy(id: UInt64) throws -> CompletedTx { - var errorCode: Int32 = -1 - let completedTxPointer = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_cancelled_transaction_by_id(pointer, id, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - guard completedTxPointer != nil else { - throw WalletErrors.cancelledTxById - } - - return CompletedTx(completedTxPointer: completedTxPointer!, isCancelled: true) - } - - func signMessage(_ message: String) throws -> Signature { - var errorCode: Int32 = -1 - let messagePointer = (message as NSString).utf8String - let resultPtr = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_sign_message(pointer, messagePointer, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - let result = String(cString: resultPtr!) - - let mutable = UnsafeMutablePointer(mutating: resultPtr!) - string_destroy(mutable) - - let (walletPubKey, publicKeyError) = self.publicKey - guard publicKeyError == nil else { - throw publicKeyError! - } - - let splitResult = result.components(separatedBy: "|") - guard splitResult.count == 2 else { - throw WalletErrors.invalidSignatureAndNonceString - } - - return Signature(hex: splitResult[0], nonce: splitResult[1], message: message, publicKey: walletPubKey!) - } - - func verifyMessageSignature(publicKey: PublicKey, signature: String, nonce: String, message: String) throws -> Bool { - var errorCode: Int32 = -1 - let messagePointer = (message as NSString).utf8String - let hexSigNoncePointer = ("\(signature)|\(nonce)" as NSString).utf8String - let result = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_verify_message_signature(pointer, publicKey.pointer, hexSigNoncePointer, messagePointer, error)}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - return result - } - - func importUtxo(_ utxo: UTXO) throws { - let privateKey = try utxo.getPrivateKey() - let sourcePublicKey = try utxo.getSourcePublicKey() - - var errorCode: Int32 = -1 - let messagePointer = (utxo.message as NSString).utf8String - - let metadataSignature = try makeCommitmentSignature(utxo: utxo) - let senderOffsetPublicKey = try utxo.makeSenderOffsetPublicKey() - - _ = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_import_external_utxo_as_non_rewindable( - pointer, - utxo.value, - privateKey.pointer, - sourcePublicKey.pointer, - nil, - metadataSignature, - senderOffsetPublicKey.pointer, - privateKey.pointer, - nil, - messagePointer, - error - )}) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - TariEventBus.postToMainThread(.requiresBackup) - TariEventBus.postToMainThread(.balanceUpdate) - TariEventBus.postToMainThread(.txListUpdate) - } - - private func makeCommitmentSignature(utxo: UTXO) throws -> OpaquePointer { - - let publicNonceVector = try ByteVector(byteArray: [UInt8](utxo.publicNonce)) - let uValueVector = try ByteVector(byteArray: [UInt8](utxo.uValue)) - let vValueVector = try ByteVector(byteArray: [UInt8](utxo.vValue)) - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let result = commitment_signature_create_from_bytes(publicNonceVector.pointer, uValueVector.pointer, vValueVector.pointer, errorCodePointer) - - guard errorCode == 0, let result = result else { - throw WalletErrors.generic(errorCode) - } - - return result - } - - func add(baseNode: BaseNode) throws { - var errorCode: Int32 = -1 - _ = withUnsafeMutablePointer(to: &errorCode) { error in - wallet_add_base_node_peer(pointer, baseNode.publicKey.pointer, baseNode.address, error) - } - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - } - - func syncBaseNode() throws { - - Wallet.baseNodeValidationStatusMap.removeAll() - var errorCode: Int32 = -1 - - let txoValidationRequestID = withUnsafeMutablePointer(to: &errorCode) { error in - wallet_start_txo_validation(pointer, error) - } - - guard errorCode == 0 else { - Wallet.baseNodeValidationStatusMap.removeAll() - throw WalletErrors.generic(errorCode) - } - - Wallet.baseNodeValidationStatusMap[.txo] = (txoValidationRequestID, nil) - TariLogger.info("txo validation started with request id \(txoValidationRequestID).") - - // tx validation - let txValidationRequestId = withUnsafeMutablePointer(to: &errorCode) { error in - wallet_start_transaction_validation(pointer, error) - } - - guard errorCode == 0 else { - Wallet.baseNodeValidationStatusMap.removeAll() - throw WalletErrors.generic(errorCode) - } - - Wallet.baseNodeValidationStatusMap[.tx] = (txValidationRequestId, nil) - TariLogger.info("tx validation started with request id \(txValidationRequestId).") - TariEventBus.postToMainThread(.baseNodeSyncStarted, sender: nil) - } - - /// Cancel all pending transactions after a certain amount of time has passed. - /// - Parameter after: Amount of time after a transaction was created - func cancelAllExpiredPendingTx(after: TimeInterval = TariSettings.shared.txTimeToExpire) throws { - guard let outboundList = pendingOutboundTxs.0?.list.0, let inboundList = pendingInboundTxs.0?.list.0 else { - throw WalletErrors.txToCancel - } - - let list: [TxProtocol] = outboundList + inboundList - - try list.forEach({ (tx) in - guard tx.status.0 == .pending, let date = tx.date.0 else { - return - } - - if date.distance(to: Date()) > after { - try cancelPendingTx(tx) - } - }) - } - - /// Cancels a transaction that's currently pending. - /// Helpful for expiring transactions where the other party has failed to sign their part after a certain amount of time. - /// - Parameter tx: Pending transaction to be cancelled - func cancelPendingTx(_ tx: TxProtocol) throws { - guard tx.status.0 == .pending else { - throw WalletErrors.cancelNonPendingTx - } - - try cancelPendingTx(tx.id.0) - } - - /// Cancels a transaction that's currently pending. - /// Helpful for expiring transactions where the other party has failed to sign their part after a certain amount of time. - /// - Parameter txId: ID of pending incoming or outgoing transaction - private func cancelPendingTx(_ txId: UInt64) throws { - var errorCode: Int32 = -1 - _ = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_cancel_pending_transaction(pointer, txId, error) - }) - - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - } - - func allUtxos() throws -> [TariUtxo] { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let result = wallet_get_all_utxos(pointer, errorCodePointer) - - guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } - return result.array() - } - - func balance() throws -> WalletBalance { - var errorCode: Int32 = -1 - let ptr = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_balance(pointer, error) - }) - - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - let bal = Balance(pointer: ptr!); - - let updatedBalance = WalletBalance(available: bal.available, incoming: bal.incoming, outgoing: bal.outgoing, timeLocked: bal.timelocked) - if walletBalance == updatedBalance - { - return walletBalance - } else { - walletBalance = updatedBalance - return walletBalance - } - } - - func recentPublicKeys(limit: Int) throws -> [PublicKey] { - - let completedPublicKeys = txsPublicKeyTimestampPair(transactions: try completedTransactions()) - let pendingInboundPublicKeys = txsPublicKeyTimestampPair(transactions: try pendingInboundTransactions()) - let pendingOutboundPublicKey = txsPublicKeyTimestampPair(transactions: try pendingOutboundTransactions()) - - let allPairs = completedPublicKeys + pendingInboundPublicKeys + pendingOutboundPublicKey - - let result = allPairs - .sorted { $0.timestamp > $1.timestamp } - .map(\.publicKey) - .reduce(into: [PublicKey]()) { result, publicKey in - guard !result.contains(publicKey) else { return } - result.append(publicKey) - } - .prefix(limit) - return Array(result) - } - - private func txsPublicKeyTimestampPair(transactions: T) -> [(publicKey: PublicKey, timestamp: UInt64)] { - transactions.list.0.compactMap { - guard let publicKey = $0.direction == .inbound ? $0.sourcePublicKey.0 : $0.destinationPublicKey.0 else { return nil } - return (publicKey: publicKey, timestamp: $0.timestamp.0) - } - } - - func previewCoinSplit(commitments: [String], splitsCount: UInt, feePerGram: UInt64) throws -> TariCoinPreview { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let vector = TariVectorWrapper(type: TariTypeTag(0)) - try vector.add(commitments: commitments) - - let result = wallet_preview_coin_split(pointer, vector.pointer, splitsCount, feePerGram, errorCodePointer) - - guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } - return result.pointee - } - - func coinSplit(commitments: [String], splitsCount: UInt, feePerGram: UInt64) throws -> UInt64 { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let vector = TariVectorWrapper(type: TariTypeTag(0)) - try vector.add(commitments: commitments) - - let result = wallet_coin_split(pointer, vector.pointer, splitsCount, feePerGram, errorCodePointer) - - guard errorCode == 0 else { throw WalletError(code: errorCode) } - return result - } - - func previewCoinsJoin(commitments: [String], feePerGram: UInt64) throws -> TariCoinPreview { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let vector = TariVectorWrapper(type: TariTypeTag(0)) - try vector.add(commitments: commitments) - - let result = wallet_preview_coin_join(pointer, vector.pointer, feePerGram, errorCodePointer) - - guard errorCode == 0, let result = result else { throw WalletError(code: errorCode) } - return result.pointee - } - - func coinsJoin(commitments: [String], feePerGram: UInt64) throws -> UInt64 { - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - - let vector = TariVectorWrapper(type: TariTypeTag(0)) - try vector.add(commitments: commitments) - - let result = wallet_coin_join(pointer, vector.pointer, feePerGram, errorCodePointer) - - guard errorCode == 0 else { throw WalletError(code: errorCode) } - return result - } - - func setKeyValue(key: String, value: String) throws -> Bool { - var errorCode: Int32 = -1 - let keyPointer = (key as NSString).utf8String - let valuePointer = (value as NSString).utf8String - - let result = withUnsafeMutablePointer(to: &errorCode) { - error in - wallet_set_key_value(pointer, keyPointer, valuePointer, error) - } - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - return result - } - - func getKeyValue(key: String) throws -> String { - var errorCode: Int32 = -1 - let keyPointer = (key as NSString).utf8String - let resultPtr = withUnsafeMutablePointer(to: &errorCode) { - error in - wallet_get_value(pointer, keyPointer, error) - } - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - - let result = String(cString: resultPtr!) - let mutable = UnsafeMutablePointer(mutating: resultPtr!) - string_destroy(mutable) - - return result - } - - func removeKeyValue(key: String) throws -> Bool { - var errorCode: Int32 = -1 - let keyPointer = (key as NSString).utf8String - - let result = withUnsafeMutablePointer(to: &errorCode) { - error in - wallet_clear_value(pointer, keyPointer, error) - } - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - return result - } - - func getRequiredConfirmationCount() throws -> UInt64 { - if let requiredConfirmationCount = requiredConfirmationCount { - return requiredConfirmationCount - } - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - wallet_get_num_confirmations_required(pointer, error) - }) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - requiredConfirmationCount = result - return result - } - - func setConfirmations(number: UInt64) throws { - var errorCode: Int32 = -1 - withUnsafeMutablePointer(to: &errorCode, { error in - wallet_set_num_confirmations_required(pointer, number, error) - }) - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - } - - func restartTxBroadcast() throws -> Bool { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - error in - wallet_restart_transaction_broadcast(pointer, error) - } - guard errorCode == 0 else { - throw WalletErrors.generic(errorCode) - } - return result - } - - func enableEncryption() throws { - var errorCode: Int32 = -1 - withUnsafeMutablePointer(to: &errorCode) { wallet_apply_encryption(pointer, Self.passphrase(), $0) } - guard errorCode > 0 else { return } - throw WalletErrors.generic(errorCode) - } - - func disableEncryption() throws { - var errorCode: Int32 = -1 - withUnsafeMutablePointer(to: &errorCode) { wallet_remove_encryption(pointer, $0) } - guard errorCode > 0 else { return } - throw WalletErrors.generic(errorCode) - } - - func startRecovery() throws -> Bool { - - let baseNode = NetworkManager.shared.selectedNetwork.selectedBaseNode - - let callback: @convention(c) (UInt8, UInt64, UInt64) -> Void = { - let state = RestoreWalletStatus(status: $0, firstValue: $1, secondValue: $2) - TariEventBus.postToMainThread(.restoreWalletStatusUpdate, sender: state) - } - - var errorCode: Int32 = -1 - let errorCodePointer = PointerHandler.pointer(for: &errorCode) - let recoveredOutputMessage = localized("transaction.one_sided_payment.note.recovered") - - let result = wallet_start_recovery( - pointer, - baseNode.publicKey.pointer, - callback, - recoveredOutputMessage, - errorCodePointer - ) - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - // MARK: - Helpers - - private static func passphrase() -> String { - guard let passphrase = AppKeychainWrapper.dbPassphrase else { - let newPassphrase = String.random(length: 32) - AppKeychainWrapper.dbPassphrase = newPassphrase - return newPassphrase - } - - return passphrase - } - - // MARK: - Deinit - - deinit { - TariLogger.warn("Wallet destroy") - wallet_destroy(pointer) - } - -} diff --git a/MobileWallet/TariLib/Wrappers/WalletTxs.swift b/MobileWallet/TariLib/Wrappers/WalletTxs.swift deleted file mode 100644 index cab1708e..00000000 --- a/MobileWallet/TariLib/Wrappers/WalletTxs.swift +++ /dev/null @@ -1,125 +0,0 @@ -// WalletTxs.swift - -/* - Package MobileWallet - Created by Jason van den Berg on 2020/05/19 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import Foundation - -extension Wallet { - - func completedTransactions() throws -> CompletedTxs { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - CompletedTxs(completedTxsPointer: wallet_get_completed_transactions(pointer, $0)) - } - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - func cancelledTransactions() throws -> CompletedTxs { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - CompletedTxs(completedTxsPointer: wallet_get_cancelled_transactions(pointer, $0), isCancelled: true) - } - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - func pendingOutboundTransactions() throws -> PendingOutboundTxs { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - PendingOutboundTxs(pendingOutboundTxsPointer: wallet_get_pending_outbound_transactions(pointer, $0)) - } - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - func pendingInboundTransactions() throws -> PendingInboundTxs { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode) { - PendingInboundTxs(pendingInboundTxsPointer: wallet_get_pending_inbound_transactions(pointer, $0)) - } - - guard errorCode == 0 else { throw WalletErrors.generic(errorCode) } - return result - } - - @available(*, deprecated, message: "Please use completedTransactions() instead") - var completedTxs: (CompletedTxs?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - CompletedTxs(completedTxsPointer: wallet_get_completed_transactions(pointer, error)) - - }) - guard errorCode == 0 else { - return (nil, WalletErrors.generic(errorCode)) - } - return (result, nil) - } - - @available(*, deprecated, message: "Please use pendingOutboundTransactions() instead") - var pendingOutboundTxs: (PendingOutboundTxs?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - PendingOutboundTxs( - pendingOutboundTxsPointer: wallet_get_pending_outbound_transactions(pointer, error)) - - }) - guard errorCode == 0 else { - return (nil, WalletErrors.generic(errorCode)) - } - return (result, nil) - } - - @available(*, deprecated, message: "Please use pendingInboundTransactions() instead") - var pendingInboundTxs: (PendingInboundTxs?, Error?) { - var errorCode: Int32 = -1 - let result = withUnsafeMutablePointer(to: &errorCode, { error in - PendingInboundTxs( - pendingInboundTxsPointer: wallet_get_pending_inbound_transactions(pointer, error)) - - }) - guard errorCode == 0 else { - return (nil, WalletErrors.generic(errorCode)) - } - return (result, nil) - } -} diff --git a/MobileWallet/TariLib/wallet.h b/MobileWallet/TariLib/wallet.h index 8eb93622..4a258821 100644 --- a/MobileWallet/TariLib/wallet.h +++ b/MobileWallet/TariLib/wallet.h @@ -69,6 +69,11 @@ struct Covenant; struct EmojiSet; +/** + * value: u64 + tag: [u8; 16] + */ +struct EncryptedValue; + struct FeePerGramStat; struct FeePerGramStatsResponse; @@ -226,10 +231,12 @@ typedef PrivateKey TariPrivateKey; * ```rust * # use tari_crypto::ristretto::*; * # use tari_crypto::keys::*; - * # use tari_crypto::common::*; + * # use tari_crypto::hash::blake2::Blake256; * # use digest::Digest; * # use tari_crypto::commitment::HomomorphicCommitmentFactory; * # use tari_crypto::ristretto::pedersen::*; + * use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; + * use tari_utilities::hex::Hex; * * let mut rng = rand::thread_rng(); * let a_val = RistrettoSecretKey::random(&mut rng); @@ -239,7 +246,9 @@ typedef PrivateKey TariPrivateKey; * let e = Blake256::digest(b"Maskerade"); * let factory = PedersenCommitmentFactory::default(); * let commitment = factory.commit(&x_val, &a_val); + * // println!("commitment: {:?}", commitment.to_hex()); * let sig = RistrettoComSig::sign(&a_val, &x_val, &a_nonce, &x_nonce, &e, &factory).unwrap(); + * // println!("sig: R {:?} u {:?} v {:?}", sig.public_nonce().to_hex(), sig.u().to_hex(), sig.v().to_hex()); * assert!(sig.verify_challenge(&commitment, &e, &factory)); * ``` * @@ -253,17 +262,18 @@ typedef PrivateKey TariPrivateKey; * # use tari_crypto::keys::*; * # use tari_crypto::commitment::HomomorphicCommitment; * # use tari_crypto::ristretto::pedersen::*; - * # use tari_crypto::common::*; + * # use tari_crypto::hash::blake2::Blake256; * # use tari_utilities::hex::*; * # use tari_utilities::ByteArray; * # use digest::Digest; + * use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; * * let commitment = - * HomomorphicCommitment::from_hex("d6cca5cc4cc302c1854a118221d6cf64d100b7da76665dae5199368f3703c665").unwrap(); + * HomomorphicCommitment::from_hex("167c6df11bf8106e89328c297e57423dc2a9be53df1ee63f6e50b4610104ab4a").unwrap(); * let r_nonce = - * HomomorphicCommitment::from_hex("9607f72d84d704825864a4455c2325509ecc290eb9419bbce7ff05f1f578284c").unwrap(); - * let u = RistrettoSecretKey::from_hex("0fd60e6479507fec35a46d2ec9da0ae300e9202e613e99b8f2b01d7ef6eccc02").unwrap(); - * let v = RistrettoSecretKey::from_hex("9ae6621dd99ecc252b90a0eb69577c6f3d2e1e8abcdd43bfd0297afadf95fb0b").unwrap(); + * HomomorphicCommitment::from_hex("4033e00996e61df2ea1abd1494b751b946663e21a20e2729c6592712beb15356").unwrap(); + * let u = RistrettoSecretKey::from_hex("f44bbc3374b172f77ffa8b904ddf0ad9f879b3e6183f9e440c57e7f01e851300").unwrap(); + * let v = RistrettoSecretKey::from_hex("fd54afb2d8008c8a3af10272b24161247b2b7ae11687813fe9fb03e34dd7f009").unwrap(); * let sig = RistrettoComSig::new(r_nonce, u, v); * let e = Blake256::digest(b"Maskerade"); * let factory = PedersenCommitmentFactory::default(); @@ -281,6 +291,8 @@ typedef ComSignature TariCommitmentSignature; typedef struct Covenant TariCovenant; +typedef struct EncryptedValue TariEncryptedValue; + typedef struct OutputFeatures TariOutputFeatures; typedef struct Contact TariContact; @@ -748,6 +760,7 @@ TariPrivateKey *private_key_from_hex(const char *key, /** * -------------------------------------------------------------------------------------------- /// + * * ------------------------------- Commitment Signature ---------------------------------------/// * Creates a TariCommitmentSignature from `u`, `v` and `public_nonce` ByteVectors * @@ -817,6 +830,56 @@ TariCovenant *covenant_create_from_bytes(const struct ByteVector *covenant_bytes */ void covenant_destroy(TariCovenant *covenant); +/** + * -------------------------------------------------------------------------------------------- /// + * --------------------------------------- EncryptedValue --------------------------------------------/// + * Creates a TariEncryptedValue from a ByteVector containing the encrypted_value bytes + * + * ## Arguments + * `encrypted_value_bytes` - The encrypted_value bytes as a ByteVector + * + * ## Returns + * `TariEncryptedValue` - Returns an encrypted value. Note that it will be ptr::null_mut() if any argument is + * null or if there was an error with the contents of bytes + * + * # Safety + * The ```encrypted_value_destroy``` function must be called when finished with a TariEncryptedValue to prevent a + * memory leak + */ +TariEncryptedValue *encrypted_value_create_from_bytes(const struct ByteVector *encrypted_value_bytes, + int *error_out); + +/** + * Creates a ByteVector containing the encrypted_value bytes from a TariEncryptedValue + * + * ## Arguments + * `encrypted_value` - The encrypted_value as a TariEncryptedValue + * + * ## Returns + * `ByteVector` - Returns a ByteVector containing the encrypted_value bytes. Note that it will be ptr::null_mut() if + * any argument is null or if there was an error with the contents of bytes + * + * # Safety + * The ```encrypted_value_destroy``` function must be called when finished with a TariEncryptedValue to prevent a + * memory leak + */ +struct ByteVector *encrypted_value_as_bytes(const TariEncryptedValue *encrypted_value, + int *error_out); + +/** + * Frees memory for a TariEncryptedValue + * + * ## Arguments + * `encrypted_value` - The pointer to a TariEncryptedValue + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void encrypted_value_destroy(TariEncryptedValue *encrypted_value); + /** * -------------------------------------------------------------------------------------------- /// * ---------------------------------- Output Features ------------------------------------------/// @@ -824,9 +887,8 @@ void covenant_destroy(TariCovenant *covenant); * * ## Arguments * `version` - The encoded value of the version as a byte - * `flags` - The encoded value of the flags as a byte + * `output_type` - The encoded value of the output type as a byte * `maturity` - The encoded value maturity as bytes - * `recovery_byte` - The encoded value of the recovery byte as a byte * `metadata` - The metadata componenet as a ByteVector. It cannot be null * `unique_id` - The unique id componenet as a ByteVector. It can be null * `mparent_public_key` - The parent public key component as a ByteVector. It can be null @@ -842,12 +904,9 @@ void covenant_destroy(TariCovenant *covenant); * prevent a memory leak */ TariOutputFeatures *output_features_create_from_bytes(unsigned char version, - unsigned char flags, + unsigned short output_type, unsigned long long maturity, - unsigned char recovery_byte, const struct ByteVector *metadata, - const struct ByteVector *unique_id, - const struct ByteVector *parent_public_key, int *error_out); /** @@ -1199,13 +1258,14 @@ int liveness_data_get_message_type(TariContactsLivenessData *liveness_data, * | 0 | Online | * | 1 | Offline | * | 2 | NeverSeen | + * | 3 | Banned | * * # Safety * The ```liveness_data_destroy``` method must be called when finished with a TariContactsLivenessData to prevent a * memory leak */ -int liveness_data_get_online_status(TariContactsLivenessData *liveness_data, - int *error_out); +const char *liveness_data_get_online_status(TariContactsLivenessData *liveness_data, + int *error_out); /** * Frees memory for a TariContactsLivenessData @@ -2068,7 +2128,7 @@ void transport_config_destroy(TariTransportConfig *transport); * `database_path` - The database path char array pointer which. This is the folder path where the * database files will be created and the application has write access to * `discovery_timeout_in_secs`: specify how long the Discovery Timeout for the wallet is. - * `network`: name of network to connect to. Valid values are: dibbler, igor, localnet, mainnet + * `network`: name of network to connect to. Valid values are: esmeralda, dibbler, igor, localnet, mainnet * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions * as an out parameter. * @@ -2174,7 +2234,12 @@ struct TariPublicKeys *comms_list_connected_public_keys(struct TariWallet *walle * } * `callback_txo_validation_complete` - The callback function pointer matching the function signature. This is called * when a TXO validation process is completed. The request_key is used to identify which request this - * callback references and the second parameter is a is a bool that returns if the validation was successful or not. + * callback references and the second parameter the second contains, weather it was successful, already busy, failed + * due to an internal failure or failed due to a communication failure. + * TxoValidationSuccess, // 0 + * TxoValidationAlreadyBusy // 1 + * TxoValidationInternalFailure // 2 + * TxoValidationCommunicationFailure // 3 * `callback_contacts_liveness_data_updated` - The callback function pointer matching the function signature. This is * called when a contact's liveness status changed. The data represents the contact's updated status information. * `callback_balance_updated` - The callback function pointer matching the function signature. This is called whenever @@ -2220,7 +2285,7 @@ struct TariWallet *wallet_create(TariCommsConfig *config, void (*callback_faux_transaction_unconfirmed)(TariCompletedTransaction*, uint64_t), void (*callback_transaction_send_result)(unsigned long long, TariTransactionSendStatus*), void (*callback_transaction_cancellation)(TariCompletedTransaction*, uint64_t), - void (*callback_txo_validation_complete)(uint64_t, bool), + void (*callback_txo_validation_complete)(uint64_t, uint64_t), void (*callback_contacts_liveness_data_updated)(TariContactsLivenessData*), void (*callback_balance_updated)(TariBalance*), void (*callback_transaction_validation_complete)(uint64_t, bool), @@ -2593,6 +2658,8 @@ void balance_destroy(TariBalance *balance); * `wallet` - The TariWallet pointer * `dest_public_key` - The TariPublicKey pointer of the peer * `amount` - The amount + * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values + * (see `Commitment::to_hex()`) * `fee_per_gram` - The transaction fee * `message` - The pointer to a char array * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions @@ -2607,6 +2674,7 @@ void balance_destroy(TariBalance *balance); unsigned long long wallet_send_transaction(struct TariWallet *wallet, TariPublicKey *dest_public_key, unsigned long long amount, + struct TariVector *commitments, unsigned long long fee_per_gram, const char *message, bool one_sided, @@ -2618,6 +2686,8 @@ unsigned long long wallet_send_transaction(struct TariWallet *wallet, * ## Arguments * `wallet` - The TariWallet pointer * `amount` - The amount + * `commitments` - A `TariVector` of "strings", tagged as `TariTypeTag::String`, containing commitment's hex values + * (see `Commitment::to_hex()`) * `fee_per_gram` - The fee per gram * `num_kernels` - The number of transaction kernels * `num_outputs` - The number of outputs @@ -2632,6 +2702,7 @@ unsigned long long wallet_send_transaction(struct TariWallet *wallet, */ unsigned long long wallet_get_fee_estimate(struct TariWallet *wallet, unsigned long long amount, + struct TariVector *commitments, unsigned long long fee_per_gram, unsigned long long num_kernels, unsigned long long num_outputs, @@ -2890,6 +2961,8 @@ TariPublicKey *wallet_get_public_key(struct TariWallet *wallet, * `script_private_key` - Tari script private key, k_S, is used to create the script signature * `covenant` - The covenant that will be executed when spending this output * `message` - The message that the transaction will have + * `encrypted_value` - Encrypted value. + * `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions * as an out parameter. * @@ -2909,6 +2982,8 @@ unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWall TariPublicKey *sender_offset_public_key, TariPrivateKey *script_private_key, TariCovenant *covenant, + TariEncryptedValue *encrypted_value, + unsigned long long minimum_value_promise, const char *message, int *error_out); diff --git a/MobileWallet/UIElements/AnimatedRefreshingView.swift b/MobileWallet/UIElements/AnimatedRefreshingView.swift index a00c0d5b..9349ca1c 100644 --- a/MobileWallet/UIElements/AnimatedRefreshingView.swift +++ b/MobileWallet/UIElements/AnimatedRefreshingView.swift @@ -128,8 +128,7 @@ private class RefreshingInnerView: UIView { statusLabel.text = localized("refresh_view.waiting_for_sender") statusLabel.textColor = Theme.shared.colors.txCellStatusLabel case .txCompleted(let confirmationCount): - guard let wallet = TariLib.shared.tariWallet, - let requiredConfirmationCount = try? wallet.getRequiredConfirmationCount() else { + guard let requiredConfirmationCount = try? Tari.shared.transactions.requiredConfirmationsCount else { statusLabel.text = localized("refresh_view.final_processing") break } diff --git a/MobileWallet/UIElements/EmojiIdView.swift b/MobileWallet/UIElements/EmojiIdView.swift index 6d0fd97c..f73c9d8a 100644 --- a/MobileWallet/UIElements/EmojiIdView.swift +++ b/MobileWallet/UIElements/EmojiIdView.swift @@ -112,21 +112,15 @@ final class EmojiIdView: UIView { prepareCondensedEmojiId(textCentered: textCentered, width: initialWidth, height: initialHeight) prepareExpandedEmojiId(height: initialHeight) } - - func setupView(pubKey: PublicKey, - textCentered: Bool, - inViewController vc: UIViewController? = nil, - initialWidth: CGFloat = CGFloat(185), - initialHeight: CGFloat = CGFloat(40), - showContainerViewBlur: Bool = true, - cornerRadius: CGFloat = 6.0) { - self.backgroundColor = .clear + + func setup(emojiID: String, hex: String, textCentered: Bool, inViewController vc: UIViewController? = nil, initialWidth: CGFloat = 185.0, initialHeight: CGFloat = 40.0, showContainerViewBlur: Bool = true, cornerRadius: CGFloat = 6.0) { + backgroundColor = .clear self.cornerRadius = cornerRadius blackoutView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:)))) superVC = vc superVC?.navigationController?.navigationBar.layer.zPosition = 0 - emojiText = pubKey.emojis.0 - pubKeyHex = pubKey.hex.0 + emojiText = emojiID + pubKeyHex = hex blackoutWhileExpanded = showContainerViewBlur // tap gesture self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) diff --git a/MobileWallet/UIElements/NavigationBar.swift b/MobileWallet/UIElements/NavigationBar.swift index fb8f1ddf..d9d85c67 100644 --- a/MobileWallet/UIElements/NavigationBar.swift +++ b/MobileWallet/UIElements/NavigationBar.swift @@ -117,7 +117,7 @@ class NavigationBar: UIView, NavigationBarProtocol { } func setProgress(_ progress: Float) { - self.progressView.setProgress(progress, animated: true) + self.progressView.setProgress(progress, animated: !isHidden) } private func setupTitle() { @@ -185,13 +185,9 @@ class NavigationBar: UIView, NavigationBarProtocol { progressView.topAnchor.constraint(equalTo: bottomAnchor).isActive = true progressView.heightAnchor.constraint(equalToConstant: 4.0).isActive = true } - - func showEmojiId(_ publicKey: PublicKey, inViewController: UIViewController) throws { - let ( _, emojisError) = publicKey.emojis - guard emojisError == nil else { throw emojisError! } - emojiIdView.setupView(pubKey: publicKey, - textCentered: true, - inViewController: inViewController) + + func showEmojiId(emojiID: String, hex: String, presenterController: UIViewController) { + emojiIdView.setup(emojiID: emojiID, hex: hex, textCentered: true, inViewController: presenterController) emojiIdView.tapToExpand = { expanded in UIView.animate(withDuration: CATransaction.animationDuration()) { [weak self] in diff --git a/MobileWallet/UIElements/NavigationBarWithSubtitle.swift b/MobileWallet/UIElements/NavigationBarWithSubtitle.swift index 06cfae70..a41020e8 100644 --- a/MobileWallet/UIElements/NavigationBarWithSubtitle.swift +++ b/MobileWallet/UIElements/NavigationBarWithSubtitle.swift @@ -50,7 +50,7 @@ final class NavigationBarWithSubtitle: NavigationBar, NavigationBarWithSubtitleP func update(subtitle: String?, isCompact: Bool) { subtitleLabel.text = subtitle - subtitleLabel.numberOfLines = isCompact ? 2 : 1 + subtitleLabel.numberOfLines = isCompact ? 3 : 1 subtitleLabel.font = isCompact ? .Avenir.medium.withSize(12.0) : .Avenir.medium.withSize(13.0) } @@ -74,6 +74,8 @@ final class NavigationBarWithSubtitle: NavigationBar, NavigationBarWithSubtitleP addSubview(subtitleLabel) subtitleLabel.translatesAutoresizingMaskIntoConstraints = false subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 9.0).isActive = true + subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0).isActive = true + subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0).isActive = true subtitleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true // Style diff --git a/MobileWallet/UIElements/TabBar/CustomTabBar.swift b/MobileWallet/UIElements/TabBar/CustomTabBar.swift index 372fdc28..fa4b4759 100644 --- a/MobileWallet/UIElements/TabBar/CustomTabBar.swift +++ b/MobileWallet/UIElements/TabBar/CustomTabBar.swift @@ -40,7 +40,8 @@ import UIKit -class CustomTabBar: UITabBar { +final class CustomTabBar: UITabBar { + override init(frame: CGRect) { super.init(frame: frame) setup() @@ -52,10 +53,18 @@ class CustomTabBar: UITabBar { } func setup() { - unselectedItemTintColor = .black - tintColor = Theme.shared.colors.homeScreenBackground - barTintColor = .white - barStyle = .black + + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = .white + appearance.stackedLayoutAppearance.normal.iconColor = .tari.greys.black + appearance.stackedLayoutAppearance.selected.iconColor = .tari.purple + + standardAppearance = appearance + + if #available(iOS 15.0, *) { + scrollEdgeAppearance = appearance + } layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0.0, height: 1.0) @@ -64,7 +73,7 @@ class CustomTabBar: UITabBar { layer.masksToBounds = false } - override open func sizeThatFits(_ size: CGSize) -> CGSize { + override func sizeThatFits(_ size: CGSize) -> CGSize { super.sizeThatFits(size) var sizeThatFits = super.sizeThatFits(size) let bottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 diff --git a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift index 6cf4eaf6..45d31be5 100644 --- a/MobileWallet/UIElements/TabBar/MenuTabBarController.swift +++ b/MobileWallet/UIElements/TabBar/MenuTabBarController.swift @@ -54,11 +54,11 @@ class MenuTabBarController: UITabBarController { var transactionsViewController = TransactionsViewController() var profileViewController = ProfileViewController() var settingsViewController = SettingsViewController() - let customTabBar = CustomTabBar(frame: .null) + let customTabBar = CustomTabBar() override func viewDidLoad() { super.viewDidLoad() - setValue(CustomTabBar(), forKey: "tabBar") + setValue(customTabBar, forKey: "tabBar") self.delegate = self homeViewController.tabBarItem.image = Theme.shared.images.homeItem diff --git a/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift b/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift index 1a7acf9a..1d4f1b64 100644 --- a/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift +++ b/MobileWallet/UIElements/Transaction/TransactionProgressPresenter.swift @@ -69,10 +69,8 @@ enum TransactionProgressPresenter { switch transactionError { case .noInternetConnection: PopUpPresenter.show(message: MessageModel(title: localized("sending_tari.error.interwebs_connection.title"), message: localized("sending_tari.error.interwebs_connection.description"), type: .error)) - Tracker.shared.track(eventWithCategory: "Transaction", action: "Transaction Failed - Tor Issue") - case .timeout, .transactionError, .unsucessfulTransaction, .unableToStartWallet: + case .timeout, .transactionError, .unsucessfulTransaction: PopUpPresenter.show(message: MessageModel(title: localized("sending_tari.error.no_connection.title"), message: localized("sending_tari.error.no_connection.description"), type: .error)) - Tracker.shared.track(eventWithCategory: "Transaction", action: "Transaction Failed - Node Issue") } } } diff --git a/MobileWallet/UIElements/UnselectableTextView.swift b/MobileWallet/UIElements/UnselectableTextView.swift new file mode 100644 index 00000000..f0275ab8 --- /dev/null +++ b/MobileWallet/UIElements/UnselectableTextView.swift @@ -0,0 +1,50 @@ +// UnselectableTextView.swift + +/* + Package MobileWallet + Created by Adrian Truszczynski on 25/07/2022 + Using Swift 5.0 + Running on macOS 12.4 + + Copyright 2019 The Tari Project + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit + +final class UnselectableTextView: UITextView { + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + guard let position = closestPosition(to: point), let range = tokenizer.rangeEnclosingPosition(position, with: .character, inDirection: .layout(.left)) else { return false } + let startIndex = offset(from: beginningOfDocument, to: range.start) + return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil + } +} diff --git a/Podfile b/Podfile index 164c7865..55b1d148 100644 --- a/Podfile +++ b/Podfile @@ -9,9 +9,8 @@ target 'MobileWallet' do pod 'lottie-ios' pod 'SwiftEntryKit', '1.2.3' pod 'ReachabilitySwift' - pod 'MatomoTracker', '~> 7.2' pod 'ZIPFoundation', '~> 0.9' - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '5.0.0' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.27.1' pod 'SwiftKeychainWrapper', '3.4.0' pod 'Giphy', '2.0.0' pod 'IPtProxy', '~> 0.1.0' diff --git a/Podfile.lock b/Podfile.lock index d0cafd5b..c2464202 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -16,9 +16,6 @@ PODS: - libwebp/demux - libwebp/webp (1.2.0) - lottie-ios (3.2.3) - - MatomoTracker (7.4.1): - - MatomoTracker/Core (= 7.4.1) - - MatomoTracker/Core (7.4.1) - OpenSSL-Universal (1.1.1200) - PINCache (3.0.3): - PINCache/Arc-exception-safe (= 3.0.3) @@ -30,9 +27,9 @@ PODS: - PINOperation (1.2.1) - QuickLayout (3.0.0) - ReachabilitySwift (5.0.0) - - Sentry (5.0.0): - - Sentry/Core (= 5.0.0) - - Sentry/Core (5.0.0) + - Sentry (7.27.1): + - Sentry/Core (= 7.27.1) + - Sentry/Core (7.27.1) - SwiftEntryKit (1.2.3): - QuickLayout (= 3.0.0) - SwiftKeychainWrapper (3.4.0) @@ -47,10 +44,9 @@ DEPENDENCIES: - Giphy (= 2.0.0) - IPtProxy (~> 0.1.0) - lottie-ios - - MatomoTracker (~> 7.2) - OpenSSL-Universal - ReachabilitySwift - - Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `5.0.0`) + - Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `7.27.1`) - SwiftEntryKit (= 1.2.3) - SwiftKeychainWrapper (= 3.4.0) - SwiftLint @@ -66,7 +62,6 @@ SPEC REPOS: - IPtProxy - libwebp - lottie-ios - - MatomoTracker - OpenSSL-Universal - PINCache - PINOperation @@ -82,12 +77,12 @@ SPEC REPOS: EXTERNAL SOURCES: Sentry: :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 5.0.0 + :tag: 7.27.1 CHECKOUT OPTIONS: Sentry: :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 5.0.0 + :tag: 7.27.1 SPEC CHECKSUMS: DeepDiff: e5ae6c50d0321568e4508cec5930b9f10bb293fc @@ -96,13 +91,12 @@ SPEC CHECKSUMS: IPtProxy: 15e1c3b6d828bc4e7dadaaeedc7ddf1377a48ca8 libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0 lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e - MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb OpenSSL-Universal: 3b8c0d6268fbd5d3ac44f97338e2fd16a73d9dbf PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 QuickLayout: 07b45a72b10083fee3f095990cfed1c1e7b27f0a ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - Sentry: 1279f37a5691c19b1b761f7e260bba5dab7c2311 + Sentry: bc644307e2eb6a4c9c55cf117a80b895bb2a25a7 SwiftEntryKit: 340713c2e4a6662c5149629990bf1088bf5f0389 SwiftKeychainWrapper: 6fc49fbf7d4a6b0772917acb0e53a1639f6078d6 SwiftLint: 99f82d07b837b942dd563c668de129a03fc3fb52 @@ -110,6 +104,6 @@ SPEC CHECKSUMS: YatLib: cd443f044b7ddc58bd17e2406e3ecaf6688880a6 ZIPFoundation: e27423c004a5a1410c15933407747374e7c6cb6e -PODFILE CHECKSUM: 2b231cf5cd04a848e11ec65d46873048d9159ff2 +PODFILE CHECKSUM: ad1d533fb59b5ed5c6db9a6ed46527db9ec1f7c3 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/UnitTests/ErrorMessageManagerTests.swift b/UnitTests/ErrorMessageManagerTests.swift index cf97aa4f..6981aafd 100644 --- a/UnitTests/ErrorMessageManagerTests.swift +++ b/UnitTests/ErrorMessageManagerTests.swift @@ -138,7 +138,7 @@ final class ErrorMessageManagerTests: XCTestCase { func testErrorModelForSeedWordsError() { - let seedWordsError = SeedWords.Error.invalidSeedPhrase + let seedWordsError = SeedWords.InternalError.invalidSeedPhrase let expectedTitle = localized("restore_from_seed_words.error.title") let expectedMessage = localized("restore_from_seed_words.error.description.invalid_seed_word") + "\n" + localized("error.code.prefix") + " " + seedWordsError.signature diff --git a/UnitTests/NetworkManagerTests.swift b/UnitTests/NetworkManagerTests.swift index e4735455..e904a198 100644 --- a/UnitTests/NetworkManagerTests.swift +++ b/UnitTests/NetworkManagerTests.swift @@ -45,7 +45,7 @@ final class NetworkManagerTests: XCTestCase { // MARK: - Properties - private let defaultNetwork = TariNetwork.dibbler + private let defaultNetwork = TariNetwork.esmeralda private var networkManager: NetworkManager! // MARK: - Setups diff --git a/UnitTests/WalletLib/SeedWordsTests.swift b/UnitTests/WalletLib/SeedWordsTests.swift index 202265a5..9634b871 100644 --- a/UnitTests/WalletLib/SeedWordsTests.swift +++ b/UnitTests/WalletLib/SeedWordsTests.swift @@ -46,11 +46,11 @@ final class SeedWordsTests: XCTestCase { func testWalletSeedInitializationWithSuccess() { let inputSeedWords = [ - "abandon", "oven", "excuse", "sustain", "next", - "story", "fruit", "tool", "ramp", "milk", - "student", "snake", "hat", "table", "hospital", - "pipe", "business", "farm", "ensure", "approve", - "uniform", "fish", "wild", "wagon" + "abandon", "crop", "company", "buddy", "drink", + "sniff", "second", "list", "zebra", "bacon", + "genuine", "industry", "unfold", "scissors", "write", + "wage", "swear", "actor", "squeeze", "share", + "replace", "sand", "travel", "goat" ] let seedWords = try? SeedWords(words: inputSeedWords) @@ -61,18 +61,18 @@ final class SeedWordsTests: XCTestCase { func testWalletSeedInitliaziationWithInvalidWord() { let inputSeedWords = [ - "abandon", "oven", "excuse", "sustain", "next", - "story", "fruit", "tool", "IAMERROR", "milk", - "student", "snake", "hat", "table", "hospital", - "pipe", "business", "farm", "ensure", "approve", - "uniform", "fish", "wild", "wagon" + "abandon", "crop", "company", "buddy", "drink", + "sniff", "second", "list", "IAMERROR", "bacon", + "genuine", "industry", "unfold", "scissors", "write", + "wage", "swear", "actor", "squeeze", "share", + "replace", "sand", "travel", "goat" ] var seedWords: SeedWords? do { seedWords = try SeedWords(words: inputSeedWords) - } catch SeedWords.Error.invalidSeedWord { + } catch SeedWords.InternalError.invalidSeedWord { } catch { XCTFail("Unexpected error") } @@ -83,15 +83,15 @@ final class SeedWordsTests: XCTestCase { func testWalletSeedInitliaziationWithNotEnoughtSeedWords() { let inputSeedWords = [ - "abandon", "oven", "excuse", "sustain", "next", - "story", "fruit", "tool", "ramp", "milk" + "abandon", "crop", "company", "buddy", "drink", + "sniff", "second", "list", "zebra", "bacon" ] var seedWords: SeedWords? do { seedWords = try SeedWords(words: inputSeedWords) - } catch SeedWords.Error.phraseIsTooShort { + } catch SeedWords.InternalError.phraseIsTooShort { } catch { XCTFail("Unexpected error") } @@ -102,18 +102,18 @@ final class SeedWordsTests: XCTestCase { func testWalletSeedInitliaziationWithTooManySeedWords() { let inputSeedWords = [ - "abandon", "oven", "excuse", "sustain", "next", - "story", "fruit", "tool", "ramp", "milk", - "student", "snake", "hat", "table", "hospital", - "pipe", "business", "farm", "ensure", "approve", - "uniform", "fish", "wild", "wagon", "abandon" + "abandon", "crop", "company", "buddy", "drink", + "sniff", "second", "list", "zebra", "bacon", + "genuine", "industry", "unfold", "scissors", "write", + "wage", "swear", "actor", "squeeze", "share", + "replace", "sand", "travel", "goat", "abandon" ] var seedWords: SeedWords? do { seedWords = try SeedWords(words: inputSeedWords) - } catch SeedWords.Error.phraseIsTooLong { + } catch SeedWords.InternalError.phraseIsTooLong { } catch { XCTFail("Unexpected error") } diff --git a/UnitTests/WalletLib/TariLibWrapperTests.swift b/UnitTests/WalletLib/TariLibWrapperTests.swift deleted file mode 100644 index 4af034c2..00000000 --- a/UnitTests/WalletLib/TariLibWrapperTests.swift +++ /dev/null @@ -1,249 +0,0 @@ -// TariLib.swift - -/* - Package MobileWalletTests - Created by Jason van den Berg on 2019/11/15 - Using Swift 5.0 - Running on macOS 10.15 - - Copyright 2019 The Tari Project - - Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -@testable import Tari_Aurora -import XCTest - -final class TariLibWrapperTests: XCTestCase { - //Use a random DB path for each test - private let dbName = "test_db" - private let backupPassword = "coolpassword" - - private func backupPath(_ storagePath: String) -> String { - return "\(storagePath)/partial_backup_\(dbName).sqlite3" - } - - private func loggingTestPath(_ storagePath: String) -> String { - return "\(storagePath)/log.txt" - } - - override func setUp() { - } - - override func tearDown() { - } - - func testByteVector() { - //Init manually. Initializing from pointers happens in priv/pub key tests. - do { - let byteVector = try ByteVector(byteArray: [0, 1, 2, 3, 4, 5]) - - let (hexString, hexError) = byteVector.hexString - if hexError != nil { - XCTFail(hexError!.localizedDescription) - } - - XCTAssertEqual(hexString, "000102030405") - } catch { - XCTFail(error.localizedDescription) - } - } - - func testPublicKey() { - //Create pub key from hex, then create hex from that to test ByteVector toString() - let originalPublicKeyHex = "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" - - do { - _ = try PublicKey(privateKey: PrivateKey()) - - let publicKey = try PublicKey(hex: originalPublicKeyHex) - let (hex, hexError) = publicKey.hex - if hexError != nil { - XCTFail(hexError!.localizedDescription) - } - XCTAssertEqual(hex, originalPublicKeyHex) - let (emojis, error) = publicKey.emojis - if error != nil { - XCTFail(error!.localizedDescription) - } - - let emojiKey = try PublicKey(emojis: emojis) - XCTAssertEqual(emojiKey.hex.0, publicKey.hex.0) - } catch { - XCTFail(error.localizedDescription) - } - - //Valid emoji ID - XCTAssertNoThrow(try PublicKey(emojis: "๐ŸŽณ๐Ÿ๐Ÿ’ธ๐Ÿผ๐Ÿท๐Ÿ’๐Ÿ”๐Ÿ’ค๐Ÿ’˜๐Ÿ”ซ๐Ÿ˜ป๐Ÿ’จ๐ŸŽฉ๐Ÿ˜ฑ๐Ÿ’ญ๐ŸŽ’๐Ÿšง๐Ÿต๐Ÿ‰๐Ÿ”ฆ๐Ÿด๐ŸŽบ๐Ÿบ๐Ÿช๐Ÿ•๐Ÿ‘”๐Ÿ„๐Ÿ๐Ÿ˜‡๐ŸŒ‚๐Ÿ‘๐Ÿญ๐Ÿ˜‡")) - XCTAssertNoThrow(try PublicKey(emojis: "๐Ÿ˜๐Ÿ’‰๐Ÿ”จ๐Ÿ†๐Ÿ’ˆ๐Ÿ†๐Ÿ’€๐ŸŽฉ๐Ÿผ๐Ÿ๐Ÿ’€๐ŸŽ‚๐Ÿ”ฑ๐Ÿป๐Ÿ‘๐Ÿ”ช๐Ÿ–๐Ÿ˜น๐Ÿ˜ป๐Ÿšœ๐Ÿญ๐ŸŽ๐Ÿ””๐Ÿ’ฉ๐Ÿš‚๐ŸŒ ๐Ÿ“ก๐Ÿ‘…๐Ÿ๐Ÿญ๐Ÿ’”๐ŸŽป๐ŸŒŠ")) - - //Invalid emoji ID - XCTAssertThrowsError(try PublicKey(emojis: "๐Ÿ’๐Ÿ‘๐Ÿ”๐Ÿ”งโŒ๐Ÿ‘‚๐Ÿฆ’๐Ÿ’‡๐Ÿ”‹๐Ÿ’ฅ๐Ÿท๐Ÿบ๐Ÿ‘”๐Ÿ˜ท๐Ÿถ๐Ÿงข๐Ÿคฉ๐Ÿ’ฅ๐ŸŽพ๐ŸŽฒ๐Ÿ€๐Ÿค ๐Ÿ’ช๐Ÿ‘ฎ๐Ÿคฏ๐ŸŽ๐Ÿ’‰๐ŸŒž๐Ÿ‰๐Ÿคท๐Ÿฆ๐Ÿ‘ฝ๐Ÿ‘ฝ")) - - //Valid deep links - XCTAssertNoThrow(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/eid/๐ŸŽณ๐Ÿ๐Ÿ’ธ๐Ÿผ๐Ÿท๐Ÿ’๐Ÿ”๐Ÿ’ค๐Ÿ’˜๐Ÿ”ซ๐Ÿ˜ป๐Ÿ’จ๐ŸŽฉ๐Ÿ˜ฑ๐Ÿ’ญ๐ŸŽ’๐Ÿšง๐Ÿต๐Ÿ‰๐Ÿ”ฆ๐Ÿด๐ŸŽบ๐Ÿบ๐Ÿช๐Ÿ•๐Ÿ‘”๐Ÿ„๐Ÿ๐Ÿ˜‡๐ŸŒ‚๐Ÿ‘๐Ÿญ๐Ÿ˜‡") - ) - XCTAssertNoThrow(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/eid/๐ŸŽณ๐Ÿ๐Ÿ’ธ๐Ÿผ๐Ÿท๐Ÿ’๐Ÿ”๐Ÿ’ค๐Ÿ’˜๐Ÿ”ซ๐Ÿ˜ป๐Ÿ’จ๐ŸŽฉ๐Ÿ˜ฑ๐Ÿ’ญ๐ŸŽ’๐Ÿšง๐Ÿต๐Ÿ‰๐Ÿ”ฆ๐Ÿด๐ŸŽบ๐Ÿบ๐Ÿช๐Ÿ•๐Ÿ‘”๐Ÿ„๐Ÿ๐Ÿ˜‡๐ŸŒ‚๐Ÿ‘๐Ÿญ๐Ÿ˜‡?amount=32.1¬e=hi%20there") - ) - XCTAssertNoThrow(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/pubkey/70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a")) - XCTAssertNoThrow(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/pubkey/70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a?amount=32.1note=hi%20there")) - //Derive a deep link from random pubkey, then init a pubkey using that deep link - XCTAssertNoThrow(try PublicKey(deeplink: PublicKey(privateKey: PrivateKey()).emojiDeeplink.0)) - XCTAssertNoThrow(try PublicKey(deeplink: PublicKey(privateKey: PrivateKey()).hexDeeplink.0)) - - //Invalid deep links - XCTAssertThrowsError(try PublicKey(deeplink: "bla bla bla")) - XCTAssertThrowsError(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork)/eid/๐Ÿ––๐Ÿฅด๐Ÿ˜๐Ÿ™ƒ๐Ÿ’ฆ๐Ÿค˜๐Ÿคœ๐Ÿ‘๐Ÿ™ƒ๐Ÿ™Œ๐Ÿ˜ฑ")) - XCTAssertThrowsError(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork)/pubkey/invalid")) - XCTAssertThrowsError(try PublicKey(deeplink: "\(TariSettings.shared.deeplinkURI)://made-up-net/pubkey/70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a")) - - //Convenience init - XCTAssertThrowsError(try PublicKey(any: "bla")) - XCTAssertThrowsError(try PublicKey(any: "Hey use this emoji ID ๐Ÿ’๐Ÿ‘๐Ÿ”๐Ÿ”งโŒ๐Ÿ‘‚๐Ÿฆ’")) - XCTAssertNoThrow(try PublicKey(any: "๐ŸŽณ๐Ÿ | ๐Ÿ’ธ๐Ÿผ๐Ÿท | ๐Ÿ’๐Ÿ”๐Ÿ’ค | ๐Ÿ’˜๐Ÿ”ซ๐Ÿ˜ป | ๐Ÿ’จ๐ŸŽฉ๐Ÿ˜ฑ | ๐Ÿ’ญ๐ŸŽ’๐Ÿšง | ๐Ÿต๐Ÿ‰๐Ÿ”ฆ | ๐Ÿด๐ŸŽบ๐Ÿบ | ๐Ÿช๐Ÿ•๐Ÿ‘” | ๐Ÿ„๐Ÿ๐Ÿ˜‡ | ๐ŸŒ‚๐Ÿ‘๐Ÿญ | ๐Ÿ˜‡")) - XCTAssertNoThrow(try PublicKey(any: "copy this: ๐Ÿ˜๐Ÿ’‰๐Ÿ”จ๐Ÿ†๐Ÿ’ˆ๐Ÿ†๐Ÿ’€๐ŸŽฉ๐Ÿผ๐Ÿ๐Ÿ’€๐ŸŽ‚๐Ÿ”ฑ๐Ÿป๐Ÿ‘๐Ÿ”ช๐Ÿ–๐Ÿ˜น๐Ÿ˜ป๐Ÿšœ๐Ÿญ๐ŸŽ๐Ÿ””๐Ÿ’ฉ๐Ÿš‚๐ŸŒ ๐Ÿ“ก๐Ÿ‘…๐Ÿ๐Ÿญ๐Ÿ’”๐ŸŽป๐ŸŒŠ please")) - XCTAssertNoThrow(try PublicKey(any: "My emojis are \"๐Ÿ˜๐Ÿ’‰๐Ÿ”จ๐Ÿ†๐Ÿ’ˆ๐Ÿ†๐Ÿ’€๐ŸŽฉ๐Ÿผ๐Ÿ๐Ÿ’€๐ŸŽ‚๐Ÿ”ฑ๐Ÿป๐Ÿ‘๐Ÿ”ช๐Ÿ–๐Ÿ˜น๐Ÿ˜ป๐Ÿšœ๐Ÿญ๐ŸŽ๐Ÿ””๐Ÿ’ฉ๐Ÿš‚๐ŸŒ ๐Ÿ“ก๐Ÿ‘…๐Ÿ๐Ÿญ๐Ÿ’”๐ŸŽป๐ŸŒŠ\"")) - XCTAssertNoThrow(try PublicKey(any: "๐Ÿ˜๐Ÿ’‰๐Ÿ”จ๐Ÿ†๐Ÿ’ˆ๐Ÿ†๐Ÿ’€๐ŸŽฉ๐Ÿผ๐Ÿ๐Ÿ’€๐ŸŽ‚๐Ÿ”ฑ๐Ÿป๐Ÿ‘๐Ÿ”ช๐Ÿ–๐Ÿ˜น bla bla bla ๐Ÿ˜ป๐Ÿšœ๐Ÿญ๐ŸŽ๐Ÿ””๐Ÿ’ฉ๐Ÿš‚๐ŸŒ ๐Ÿ“ก๐Ÿ‘…๐Ÿ๐Ÿญ๐Ÿ’”๐ŸŽป๐ŸŒŠ")) - XCTAssertNoThrow(try PublicKey(any: "Please send me 1234. My emojis are ๐ŸŽณ๐Ÿ๐Ÿ’ธ๐Ÿผ๐Ÿท๐Ÿ’๐Ÿ”๐Ÿ’ค๐Ÿ’˜ and here are the rest ๐Ÿ”ซ๐Ÿ˜ป๐Ÿ’จ๐ŸŽฉ๐Ÿ˜ฑ๐Ÿ’ญ๐ŸŽ’๐Ÿšง๐Ÿต๐Ÿ‰๐Ÿ”ฆ๐Ÿด๐ŸŽบ๐Ÿบ๐Ÿช๐Ÿ•๐Ÿ‘”๐Ÿ„๐Ÿ๐Ÿ˜‡๐ŸŒ‚๐Ÿ‘๐Ÿญ๐Ÿ˜‡")) - } - - func testOldEmojiSet() { - do { - _ = try PublicKey(any: "โšฝ๐Ÿงฃ๐Ÿ‘‚๐Ÿค๐Ÿง๐Ÿณ๐Ÿฆ„๐ŸŽฃ๐Ÿ˜›๐ŸŽป๐Ÿ„๐Ÿšงโ›บ๐Ÿง ๐Ÿ””๐Ÿงข๐Ÿ„๐Ÿ’‰๐Ÿ•™๐Ÿ”๐Ÿšช๐Ÿงค๐Ÿช๐Ÿš’๐ŸŒ๐Ÿ‘Š๐Ÿฅœ๐Ÿ‘ถ๐Ÿคช๐Ÿ“Ž๐Ÿš๐Ÿฆ€๐ŸŽ") - } catch { - if case PublicKeyError.invalidEmojiSet = error { - //Correct error - } else { - XCTFail("Invalid emoji set should throw error") - } - } - - do { - _ = try PublicKey(any: "send me 12 โšฝ๐Ÿงฃ๐Ÿ‘‚๐Ÿค๐Ÿง๐Ÿณ๐Ÿฆ„๐ŸŽฃ๐Ÿ˜›๐ŸŽป๐Ÿ„๐Ÿšงโ›บ๐Ÿง ๐Ÿ””๐Ÿงข๐Ÿ„๐Ÿ’‰๐Ÿ•™๐Ÿ”๐Ÿšช๐Ÿงค๐Ÿช๐Ÿš’๐ŸŒ๐Ÿ‘Š๐Ÿฅœ๐Ÿ‘ถ๐Ÿคช๐Ÿ“Ž๐Ÿš๐Ÿฆ€๐ŸŽ") - } catch { - if case PublicKeyError.invalidEmojiSet = error { - //Correct error - - } else { - XCTFail("Invalid emoji set should throw error") - } - } - } - - func testDeepLink() { - do { - let params = try DeepLinkParams(deeplink: "\(TariSettings.shared.deeplinkURI)://\(NetworkManager.shared.selectedNetwork.name)/pubkey/70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a?amount=60.50¬e=hi%20there") - - XCTAssertEqual(60500000, params.amount.rawValue) - } catch { - XCTFail(error.localizedDescription) - } - } - - func testBaseNode() { - //Invalid peers - XCTAssertThrowsError(try BaseNode(name: "Test1", peer: "bla bla bla")) - XCTAssertThrowsError(try BaseNode(name: "Test2", peer: "5edb022af1c21d644dfceeea2fcc7d3fac7a57ab44cf775b9a6f692cb75ed767::/onion3/vjkj44zpriqzrlve2qbiasrluaaxagrb6iuavzaascbujri6gw3rcmyd")) - XCTAssertThrowsError(try BaseNode(name: "Test3", peer: "5edb022af1c21d644dfceeea2fcc7d3fac7a57ab44cf775b9a6f692cb75ed767::vjkj44zpriqzrlve2qbiasrluaaxagrb6iuavzaascbujri6gw3rcmyd:18141")) - - //Valid peer - XCTAssertNoThrow(try BaseNode(name: "Test4", peer:"2e93c460df49d8cfbbf7a06dd9004c25a84f92584f7d0ac5e30bd8e0beee9a43::/onion3/nuuq3e2olck22rudimovhmrdwkmjncxvwdgbvfxhz6myzcnx2j4rssyd:18141")) - } - - func testMicroTari() { - let microTari = MicroTari(98234567) - XCTAssert(microTari.taris == 98.234567) - XCTAssert(MicroTari.toTariNumber(NSNumber(3)) == 3000000) - //Check 2 most common local formats - XCTAssert( - microTari.formatted == "98.23" - || microTari.formatted == "98,23" - ) - XCTAssert( - microTari.formattedWithOperator == "+ 98.234567" - || microTari.formattedWithOperator == "+ 98,234567" - ) - XCTAssert( - microTari.formattedWithNegativeOperator == "- 98.234567" - || microTari.formattedWithNegativeOperator == "- 98,234567" - ) - XCTAssert( - microTari.formattedPrecise == "98.234567" - || microTari.formattedPrecise == "98,234567" - ) - XCTAssert( - MicroTari.convertToNumber("10.03") == NSNumber(10.03) - || MicroTari.convertToNumber("10,03") == NSNumber(10.03) - ) - XCTAssert( - MicroTari.convertToString(NSNumber(10.03), minimumFractionDigits: 2) == "10.03" - || MicroTari.convertToString(NSNumber(10.03), minimumFractionDigits: 2) == "10,03" - ) - XCTAssert( - MicroTari.convertToString(NSNumber(10), minimumFractionDigits: 1) == "10.0" - || MicroTari.convertToString(NSNumber(10), minimumFractionDigits: 1) == "10,0" - ) - XCTAssert(MicroTari.convertToString(NSNumber(10.0), minimumFractionDigits: 0) == "10") - XCTAssertNoThrow(try MicroTari(tariValue: "1234567898")) - XCTAssertThrowsError(try MicroTari(tariValue: "1234567898765432123567")) //Too large to be converted to uint64 in micro tari - XCTAssertNoThrow(try MicroTari(decimalValue: 2)) - XCTAssertNoThrow(try MicroTari(decimalValue: 1.1234)) - XCTAssertThrowsError(try MicroTari(decimalValue: 0.123456789)) - } - - func restoreWallet(completion: @escaping ((_ wallet: Wallet?, _ error: Error?) -> Void)) { - ICloudBackup.shared.restoreWallet(password: backupPassword, completion: { error in - var commsConfig: CommsConfig? - do { - let transport = TransportConfig() - let address = transport.address.0 - commsConfig = try CommsConfig( - transport: transport, - databaseFolderPath: TariSettings.testStoragePath, - databaseName: self.dbName, - publicAddress: address, - discoveryTimeoutSec: TariSettings.shared.discoveryTimeoutSec, - safMessageDurationSec: TariSettings.shared.safMessageDurationSec - ) - } catch { - completion(nil, error) - return - } - - do { - let wallet = try Wallet(commsConfig: commsConfig!, loggingFilePath: self.loggingTestPath(TariSettings.testStoragePath), seedWords: nil, networkName: TariNetwork.dibbler.name) - completion(wallet, error) - } catch { - completion(nil, error) - return - } - }) - } -} diff --git a/dependencies.env b/dependencies.env index 2d463b06..9a317f4c 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1 +1 @@ -FFI_VERSION="0.32.12" \ No newline at end of file +FFI_VERSION="0.38.5" \ No newline at end of file