diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 9b69fdad4d..efcba1a735 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -227,7 +227,6 @@ 11B3525F2CAA4B98F29705A7 /* SendViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BEF6F80BDF166173819 /* SendViewModel.swift */; }; 11B35262A6D3AB8C41E2E245 /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E87C15A3AE82F471007 /* AccountStorage.swift */; }; 11B35262B98EA59CDA12DF97 /* WalletConnectSessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355C1E3C922BAE804AAF9 /* WalletConnectSessionStorage.swift */; }; - 11B35264EC1BABABCDDD1F67 /* TonIncomingTransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3577A9294A3EE662872D7 /* TonIncomingTransactionRecord.swift */; }; 11B3526AA8E758606BC0CE38 /* CexWithdrawService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3548F0E1223B08D3B7F0C /* CexWithdrawService.swift */; }; 11B3527C3BD088DCCA6959C3 /* ModuleUnlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FF02BBEDAEF446D0610 /* ModuleUnlockView.swift */; }; 11B3527D20636D21F0F45C80 /* CurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35779E6353B98B298FF29 /* CurrentDateProvider.swift */; }; @@ -308,7 +307,6 @@ 11B353577381981235B90A82 /* ThemeListStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356EF92FFD23F4385A991 /* ThemeListStyle.swift */; }; 11B3535ABA61D7BD84EE500C /* NftEventMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352BACB38FE566F6F575B /* NftEventMetadata.swift */; }; 11B3535C10D649F8CD1BCDAF /* HsLabelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EDE31BA3EF80F78859A /* HsLabelProvider.swift */; }; - 11B3535D6A72ED1D564A0F7C /* TonOutgoingTransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3542B81C83F855FB3CD6C /* TonOutgoingTransactionRecord.swift */; }; 11B3535EF39FCD22171AC21C /* FaqService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352B4E116BEC01B972A39 /* FaqService.swift */; }; 11B3536008462BFF8CA245BE /* PublicKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C285327E4099656DBA8 /* PublicKeysViewModel.swift */; }; 11B353656027333CBE1C33AE /* CexWithdrawNetworkSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351C522855F29C6B038D3 /* CexWithdrawNetworkSelectViewController.swift */; }; @@ -753,8 +751,6 @@ 11B35897566548048FCEC11E /* WalletBlockchainElementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35AE5785634316A1A5DA8 /* WalletBlockchainElementService.swift */; }; 11B35898D710EC75D922785D /* WelcomeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A05B93CB243B6404C4A /* WelcomeTextView.swift */; }; 11B3589B3C808CAD4069836B /* RestoreBinanceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E2D539ACED30C947F2C /* RestoreBinanceService.swift */; }; - 11B3589C124F6BBDDBB144F4 /* TonOutgoingTransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3542B81C83F855FB3CD6C /* TonOutgoingTransactionRecord.swift */; }; - 11B3589CF4D819A0430DE3D9 /* TonIncomingTransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3577A9294A3EE662872D7 /* TonIncomingTransactionRecord.swift */; }; 11B358A1409FB729B1C3013E /* PoolSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EC9E0E936067225C787 /* PoolSource.swift */; }; 11B358A6F7662B69D358069A /* SelfSizedSectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3538D10D6256399A51F61 /* SelfSizedSectionsTableView.swift */; }; 11B358A8C0BE6B957BDE1776 /* AlertViewControllerNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3511E005E93914B2A0759 /* AlertViewControllerNew.swift */; }; @@ -2660,16 +2656,6 @@ D0C226172A66A6F3007101F7 /* PersonalSupportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C226152A66A6F3007101F7 /* PersonalSupportService.swift */; }; D0C226192A66A703007101F7 /* PersonalSupportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C226182A66A703007101F7 /* PersonalSupportViewModel.swift */; }; D0C2261A2A66A703007101F7 /* PersonalSupportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C226182A66A703007101F7 /* PersonalSupportViewModel.swift */; }; - D0D343C72C6E1ECB008F1D47 /* EventSource in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343C62C6E1ECB008F1D47 /* EventSource */; }; - D0D343C92C6E1ECB008F1D47 /* StreamURLSessionTransport in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343C82C6E1ECB008F1D47 /* StreamURLSessionTransport */; }; - D0D343CB2C6E1ECB008F1D47 /* TonAPI in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343CA2C6E1ECB008F1D47 /* TonAPI */; }; - D0D343CD2C6E1ECB008F1D47 /* TonStreamingAPI in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343CC2C6E1ECB008F1D47 /* TonStreamingAPI */; }; - D0D343CF2C6E1EEB008F1D47 /* EventSource in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343CE2C6E1EEB008F1D47 /* EventSource */; }; - D0D343D12C6E1EEB008F1D47 /* StreamURLSessionTransport in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343D02C6E1EEB008F1D47 /* StreamURLSessionTransport */; }; - D0D343D32C6E1EEB008F1D47 /* TonAPI in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343D22C6E1EEB008F1D47 /* TonAPI */; }; - D0D343D52C6E1EEB008F1D47 /* TonStreamingAPI in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343D42C6E1EEB008F1D47 /* TonStreamingAPI */; }; - D0D343D82C6E1F83008F1D47 /* TonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343D72C6E1F83008F1D47 /* TonSwift */; }; - D0D343DA2C6E1F98008F1D47 /* TonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D0D343D92C6E1F98008F1D47 /* TonSwift */; }; D0D5BCBC2976CB9F00587FDB /* PasswordInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D5BCBB2976CB9F00587FDB /* PasswordInputView.swift */; }; D0D5BCBD2976CB9F00587FDB /* PasswordInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D5BCBB2976CB9F00587FDB /* PasswordInputView.swift */; }; D0D5BCC02976D3B300587FDB /* PsswordInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D5BCBF2976D3B300587FDB /* PsswordInputCell.swift */; }; @@ -2750,6 +2736,14 @@ D3402AF22BF5D59D003BF6F8 /* WatchlistModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF02BF5D59D003BF6F8 /* WatchlistModifier.swift */; }; D3402AF72BF71C11003BF6F8 /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; D3402AF82BF71C11003BF6F8 /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; + D34338E52C8703F6001FCAD2 /* TonKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338E42C8703F6001FCAD2 /* TonKitManager.swift */; }; + D34338E62C8703F6001FCAD2 /* TonKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338E42C8703F6001FCAD2 /* TonKitManager.swift */; }; + D34338E82C871344001FCAD2 /* TonTransactionAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338E72C871344001FCAD2 /* TonTransactionAdapter.swift */; }; + D34338E92C871344001FCAD2 /* TonTransactionAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338E72C871344001FCAD2 /* TonTransactionAdapter.swift */; }; + D34338EB2C8714A2001FCAD2 /* TonEventConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338EA2C8714A2001FCAD2 /* TonEventConverter.swift */; }; + D34338EC2C8714A2001FCAD2 /* TonEventConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338EA2C8714A2001FCAD2 /* TonEventConverter.swift */; }; + D34338EE2C885895001FCAD2 /* JettonAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338ED2C885895001FCAD2 /* JettonAdapter.swift */; }; + D34338EF2C885895001FCAD2 /* JettonAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34338ED2C885895001FCAD2 /* JettonAdapter.swift */; }; D3447DEA25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; }; D3447DEB25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; }; D34903172BE8DF48005F147B /* BinanceSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903162BE8DF48005F147B /* BinanceSendHandler.swift */; }; @@ -3377,7 +3371,6 @@ 11B35420841B4F9B886A6507 /* DuressModeIntroView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeIntroView.swift; sourceTree = ""; }; 11B35420B8191814543CBFA8 /* AddressInputCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressInputCell.swift; sourceTree = ""; }; 11B3542B6FE4B4F0C0B65369 /* FeeData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeeData.swift; sourceTree = ""; }; - 11B3542B81C83F855FB3CD6C /* TonOutgoingTransactionRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TonOutgoingTransactionRecord.swift; sourceTree = ""; }; 11B3543968337A40168D3EB0 /* MarkdownParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownParser.swift; sourceTree = ""; }; 11B3544AC69419F31F20F34E /* TransactionFilterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionFilterViewModel.swift; sourceTree = ""; }; 11B35450456BE5E3EE8F7391 /* Faq.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Faq.swift; sourceTree = ""; }; @@ -3503,7 +3496,6 @@ 11B3576FCFC9394BA37975FC /* BackupVerifyWordsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupVerifyWordsViewModel.swift; sourceTree = ""; }; 11B357736B8C29DF38F5DCBA /* AlertViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = ""; }; 11B35779E6353B98B298FF29 /* CurrentDateProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentDateProvider.swift; sourceTree = ""; }; - 11B3577A9294A3EE662872D7 /* TonIncomingTransactionRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TonIncomingTransactionRecord.swift; sourceTree = ""; }; 11B35785C46145F04D21302C /* SendEvmTransactionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendEvmTransactionViewModel.swift; sourceTree = ""; }; 11B35785DD2AF78CEBD800F5 /* BackupVerifyWordsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupVerifyWordsModule.swift; sourceTree = ""; }; 11B357889F003A0B33D9DF27 /* PriceChangeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PriceChangeType.swift; sourceTree = ""; }; @@ -4575,6 +4567,10 @@ D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistViewModel.swift; sourceTree = ""; }; D3402AF02BF5D59D003BF6F8 /* WatchlistModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistModifier.swift; sourceTree = ""; }; D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistManager.swift; sourceTree = ""; }; + D34338E42C8703F6001FCAD2 /* TonKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TonKitManager.swift; sourceTree = ""; }; + D34338E72C871344001FCAD2 /* TonTransactionAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TonTransactionAdapter.swift; sourceTree = ""; }; + D34338EA2C8714A2001FCAD2 /* TonEventConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TonEventConverter.swift; sourceTree = ""; }; + D34338ED2C885895001FCAD2 /* JettonAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JettonAdapter.swift; sourceTree = ""; }; D34903162BE8DF48005F147B /* BinanceSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinanceSendHandler.swift; sourceTree = ""; }; D34903192BE8DF5F005F147B /* BinancePreSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinancePreSendHandler.swift; sourceTree = ""; }; D34A29B42BFB4AE200F63036 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppWidget.strings; sourceTree = ""; }; @@ -4726,7 +4722,6 @@ D023D2652A24B9DC004F65B0 /* TronKit in Frameworks */, D3C187BC2907CFC200FE1900 /* Checkpoints in Frameworks */, D3AF5A9329FFD87500C1399E /* RxSwift in Frameworks */, - D0D343D12C6E1EEB008F1D47 /* StreamURLSessionTransport in Frameworks */, D3604E5728F02B280066C366 /* NftKit in Frameworks */, D3C187E0290FD00E00FE1900 /* ThemeKit in Frameworks */, 6BDDB59C2C1816B500DE9D56 /* TonKit in Frameworks */, @@ -4736,17 +4731,13 @@ D3604E5528F02B280066C366 /* Eip20Kit in Frameworks */, D3604E4528F02A260066C366 /* EvmKit in Frameworks */, D3604E9028F03DC00066C366 /* MarketKit in Frameworks */, - D0D343DA2C6E1F98008F1D47 /* TonSwift in Frameworks */, D36E0C2C28D084CB00B622B9 /* CollectionViewCenteredFlowLayout in Frameworks */, D3604E8A28F03DBF0066C366 /* BitcoinKit in Frameworks */, - D0D343D32C6E1EEB008F1D47 /* TonAPI in Frameworks */, - D0D343D52C6E1EEB008F1D47 /* TonStreamingAPI in Frameworks */, D3604E9A28F03DC00066C366 /* Chart in Frameworks */, D3604E8E28F03DBF0066C366 /* LitecoinKit in Frameworks */, D3AF5A8F29FFD87400C1399E /* RxCocoa in Frameworks */, D0EC34DD2A4450D200BB308B /* HCaptcha in Frameworks */, D38405B9218317DF007D50AD /* AVFoundation.framework in Frameworks */, - D0D343CF2C6E1EEB008F1D47 /* EventSource in Frameworks */, D3604E5B28F02B280066C366 /* OneInchKit in Frameworks */, D3604EA028F03DC00066C366 /* DashKit in Frameworks */, D3C187B52907A63600FE1900 /* HdWalletKit in Frameworks */, @@ -4780,7 +4771,6 @@ 6BDA29AD29D6F384003847ED /* ECashKit in Frameworks */, D3604E4D28F02AB40066C366 /* NftKit in Frameworks */, D3C187CF290FCF2D00FE1900 /* ThemeKit in Frameworks */, - D0D343C92C6E1ECB008F1D47 /* StreamURLSessionTransport in Frameworks */, D0DA740F272A6EFC0072BE86 /* UnicodeURL in Frameworks */, D38406A721831B3D007D50AD /* LocalAuthentication.framework in Frameworks */, 6BDDB5A12C1AC73D00DE9D56 /* TonKit in Frameworks */, @@ -4790,17 +4780,13 @@ D0EC34DB2A4450B100BB308B /* HCaptcha in Frameworks */, D3604E7028F03AC80066C366 /* MarketKit in Frameworks */, D3E323CA2AE7B8F400F73914 /* KeychainAccess in Frameworks */, - D0D343D82C6E1F83008F1D47 /* TonSwift in Frameworks */, D36E0C2A28D084AB00B622B9 /* CollectionViewCenteredFlowLayout in Frameworks */, D3604E6628F02D9A0066C366 /* BitcoinKit in Frameworks */, - D0D343CB2C6E1ECB008F1D47 /* TonAPI in Frameworks */, - D0D343CD2C6E1ECB008F1D47 /* TonStreamingAPI in Frameworks */, D3604E7F28F03C1D0066C366 /* Chart in Frameworks */, D3AF5A8D29FFD85800C1399E /* RxSwift in Frameworks */, D3604E6C28F02E3F0066C366 /* LitecoinKit in Frameworks */, D38406A821831B3D007D50AD /* AVFoundation.framework in Frameworks */, D3604E5328F02B150066C366 /* OneInchKit in Frameworks */, - D0D343C72C6E1ECB008F1D47 /* EventSource in Frameworks */, D3604E8828F03D9E0066C366 /* DashKit in Frameworks */, D3C187B32907A60800FE1900 /* HdWalletKit in Frameworks */, D3604E5028F02AE70066C366 /* UniswapKit in Frameworks */, @@ -5421,6 +5407,7 @@ 6BB14F6A2BF49E7100E879B2 /* WalletButtonHiddenManager.swift */, D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */, D3384D502C0703B400515664 /* PriceChangeModeManager.swift */, + D34338E42C8703F6001FCAD2 /* TonKitManager.swift */, ); path = Managers; sourceTree = ""; @@ -6133,9 +6120,7 @@ 11B358FF5AC3462CCE9C318E /* Ton */ = { isa = PBXGroup; children = ( - 11B3542B81C83F855FB3CD6C /* TonOutgoingTransactionRecord.swift */, 11B35A67733FE53B31E5EB3A /* TonTransactionRecord.swift */, - 11B3577A9294A3EE662872D7 /* TonIncomingTransactionRecord.swift */, ); path = Ton; sourceTree = ""; @@ -6567,6 +6552,9 @@ 3AF9F617253EF555000626A8 /* ZcashTransactionPool.swift */, ABC9A4C563432A34A634B82A /* ECashAdapter.swift */, 11B35F9B75F6663FAFCA3177 /* TonAdapter.swift */, + D34338E72C871344001FCAD2 /* TonTransactionAdapter.swift */, + D34338EA2C8714A2001FCAD2 /* TonEventConverter.swift */, + D34338ED2C885895001FCAD2 /* JettonAdapter.swift */, ); path = Adapters; sourceTree = ""; @@ -8882,11 +8870,6 @@ D08C93B02B91E3B400A7D1D5 /* Hodler */, 6BBCE4A22BDA419200ABBD55 /* Web3Wallet */, 6BDDB59B2C1816B500DE9D56 /* TonKit */, - D0D343CE2C6E1EEB008F1D47 /* EventSource */, - D0D343D02C6E1EEB008F1D47 /* StreamURLSessionTransport */, - D0D343D22C6E1EEB008F1D47 /* TonAPI */, - D0D343D42C6E1EEB008F1D47 /* TonStreamingAPI */, - D0D343D92C6E1F98008F1D47 /* TonSwift */, ); productName = Wallet; productReference = D38405CE218317DF007D50AD /* Unstoppable D.app */; @@ -8950,11 +8933,6 @@ D08C93AE2B91E39E00A7D1D5 /* Hodler */, 6BBCE4A42BDA419B00ABBD55 /* Web3Wallet */, 6BDDB5A02C1AC73D00DE9D56 /* TonKit */, - D0D343C62C6E1ECB008F1D47 /* EventSource */, - D0D343C82C6E1ECB008F1D47 /* StreamURLSessionTransport */, - D0D343CA2C6E1ECB008F1D47 /* TonAPI */, - D0D343CC2C6E1ECB008F1D47 /* TonStreamingAPI */, - D0D343D72C6E1F83008F1D47 /* TonSwift */, ); productName = Wallet; productReference = D38406BE21831B3D007D50AD /* Unstoppable.app */; @@ -9126,8 +9104,6 @@ D08C93AD2B91E37E00A7D1D5 /* XCRemoteSwiftPackageReference "Hodler" */, 6BF66DD82BA1A73300963242 /* XCRemoteSwiftPackageReference "ObjectMapper" */, 6BDDB59F2C1AC73200DE9D56 /* XCRemoteSwiftPackageReference "TonKit" */, - D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */, - D0D343D62C6E1F83008F1D47 /* XCRemoteSwiftPackageReference "ton-swift" */, ); productRefGroup = D3285F4320BD158E00644076 /* Products */; projectDirPath = ""; @@ -9886,6 +9862,7 @@ ABC9A6BC79804B3D3AAFA8F1 /* SendZcashService.swift in Sources */, ABC9A55FD66CB6374F2D520D /* SendZcashViewController.swift in Sources */, ABC9A852D667D38030B7EF39 /* SendConfirmationModule.swift in Sources */, + D34338E62C8703F6001FCAD2 /* TonKitManager.swift in Sources */, ABC9A4BD4CA7A7872CE6167E /* BaseSendViewController.swift in Sources */, ABC9A78CFF8B232D330EC7B5 /* DiffLabel.swift in Sources */, ABC9ADE1C8B18509F080FD11 /* ProFeaturesStorage.swift in Sources */, @@ -10177,6 +10154,7 @@ 11B35D6646585145422DA2AD /* HsToolKit.swift in Sources */, 11B35DB3DC5CAC18264E82D7 /* Publisher.swift in Sources */, 11B3508877059D245C78131E /* EvmKit.swift in Sources */, + D34338EF2C885895001FCAD2 /* JettonAdapter.swift in Sources */, 11B35819C7596909297311B2 /* Eip20Kit.swift in Sources */, 11B35EA628D9401F5C3A9CB8 /* NftKit.swift in Sources */, 11B35BC602EA104EE1C0540C /* BinanceChainKit.swift in Sources */, @@ -10200,6 +10178,7 @@ ABC9A0073333D3DEC2797D15 /* BackupCloudPassphraseViewController.swift in Sources */, ABC9AE472D9105F6FDAECD42 /* BackupCloudPassphraseService.swift in Sources */, ABC9ABE3189E497EC732B331 /* BackupCloudPassphraseViewModel.swift in Sources */, + D34338EC2C8714A2001FCAD2 /* TonEventConverter.swift in Sources */, ABC9A2A249A94B271F56EBD0 /* BackupCryptoHelper.swift in Sources */, ABC9A543EB59D153FAD103F6 /* KdfParams.swift in Sources */, ABC9AEA715281555878BF2A9 /* BackupCrypto.swift in Sources */, @@ -10294,6 +10273,7 @@ 11B358E4C529863AFFF8806F /* ReceiveAddressViewModel.swift in Sources */, 11B35EE923002E9FCC413422 /* ReceiveAddressViewItemFactory.swift in Sources */, 11B35D8E976E984302A81F8B /* ReceiveTokenViewModel.swift in Sources */, + D34338E92C871344001FCAD2 /* TonTransactionAdapter.swift in Sources */, 11B35AC1D930F8C6ECC536FC /* RecieveSelectorViewModel.swift in Sources */, 11B35434C09F1E3818DC857B /* ReceiveDerivationViewModel.swift in Sources */, 11B35A92EEEAA35262FB375B /* ReceiveSelectorViewController.swift in Sources */, @@ -10543,9 +10523,7 @@ 11B35ACE7B126DCF9F7F1A19 /* SearchBar.swift in Sources */, 11B3546CB3C043D22A5F7A88 /* TransactionsViewModel.swift in Sources */, 11B35071705455BC25C214D2 /* TonAdapter.swift in Sources */, - 11B3535D6A72ED1D564A0F7C /* TonOutgoingTransactionRecord.swift in Sources */, 11B35C60FE9B94994FCCB0CB /* TonTransactionRecord.swift in Sources */, - 11B3589CF4D819A0430DE3D9 /* TonIncomingTransactionRecord.swift in Sources */, D389BC502C0DEF1800724504 /* MarketAdvancedSearchResultsView.swift in Sources */, 11B35927C89712EF8ED36981 /* InputRowModifier.swift in Sources */, 11B359926B194C0207B1C8E6 /* PreSendView.swift in Sources */, @@ -11342,6 +11320,7 @@ ABC9A0A3A52AD41643D67D3D /* SingleLineFormTextView.swift in Sources */, ABC9AC839A67BDEABD24CD7A /* SendMemoInputCell.swift in Sources */, ABC9ACADCCE9CEB2588D91D5 /* SendMemoInputViewModel.swift in Sources */, + D34338E52C8703F6001FCAD2 /* TonKitManager.swift in Sources */, ABC9AEC9C350F3CD059C9716 /* SendMemoInputService.swift in Sources */, ABC9ACDD64F14C0D65A5CFBC /* SendZcashFactory.swift in Sources */, ABC9A51F2E7EB0B477EBE708 /* SendZcashService.swift in Sources */, @@ -11633,6 +11612,7 @@ 11B3590B327A6CC55E64B8B2 /* HsToolKit.swift in Sources */, 11B35A8D8CA77B42650F4C03 /* Publisher.swift in Sources */, 11B35C343013C028B5D20B4A /* EvmKit.swift in Sources */, + D34338EE2C885895001FCAD2 /* JettonAdapter.swift in Sources */, 11B35521FDF21F9B1667AF72 /* Eip20Kit.swift in Sources */, 11B355734D16C412220BBEBD /* NftKit.swift in Sources */, 11B35146CA9BE897C858AB73 /* BinanceChainKit.swift in Sources */, @@ -11656,6 +11636,7 @@ ABC9A2B6F1CF39D5CE9EA489 /* BackupCloudPassphraseViewController.swift in Sources */, ABC9AB308727D81FBB8EBCDD /* BackupCloudPassphraseService.swift in Sources */, ABC9A191F1E62A20D2D38262 /* BackupCloudPassphraseViewModel.swift in Sources */, + D34338EB2C8714A2001FCAD2 /* TonEventConverter.swift in Sources */, ABC9AB83EE3F909BD80E0539 /* BackupCryptoHelper.swift in Sources */, ABC9A60BA5DF119C7FC8A859 /* KdfParams.swift in Sources */, ABC9A6887B716464A5813EE9 /* BackupCrypto.swift in Sources */, @@ -11750,6 +11731,7 @@ 11B35F4294930802EC8C9CEA /* ReceiveAddressModule.swift in Sources */, 11B3550C46C5811A7540A934 /* ReceiveAddressService.swift in Sources */, 11B3543A420A23064B056925 /* ReceiveAddressViewModel.swift in Sources */, + D34338E82C871344001FCAD2 /* TonTransactionAdapter.swift in Sources */, 11B35B0B003AA4C6F1A4CD36 /* ReceiveAddressViewItemFactory.swift in Sources */, 11B35EF433960198D484E5E8 /* ReceiveTokenViewModel.swift in Sources */, 11B35979C6916A0A5D361879 /* RecieveSelectorViewModel.swift in Sources */, @@ -12001,10 +11983,8 @@ 11B358781EBEFCE7CED000F0 /* SearchBar.swift in Sources */, 11B358D91C9D8102C46B97ED /* TransactionsViewModel.swift in Sources */, 11B35D88633A14FD13E91702 /* TonAdapter.swift in Sources */, - 11B3589C124F6BBDDBB144F4 /* TonOutgoingTransactionRecord.swift in Sources */, D389BC4F2C0DEF1800724504 /* MarketAdvancedSearchResultsView.swift in Sources */, 11B35EB4CAA93773DF09B479 /* TonTransactionRecord.swift in Sources */, - 11B35264EC1BABABCDDD1F67 /* TonIncomingTransactionRecord.swift in Sources */, 11B359D912BC5502A9FA0E57 /* InputRowModifier.swift in Sources */, 11B35283F8170DB664A77C2B /* PreSendView.swift in Sources */, 11B3598EE09251933E7FFD5D /* PreSendViewModel.swift in Sources */, @@ -12914,7 +12894,7 @@ repositoryURL = "https://github.com/horizontalsystems/TonKit.Swift"; requirement = { kind = exactVersion; - version = 0.2.0; + version = 1.0.0; }; }; 6BF66DD82BA1A73300963242 /* XCRemoteSwiftPackageReference "ObjectMapper" */ = { @@ -12941,22 +12921,6 @@ version = 2.0.2; }; }; - D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/tonkeeper/ton-api-swift.git"; - requirement = { - kind = exactVersion; - version = 0.1.5; - }; - }; - D0D343D62C6E1F83008F1D47 /* XCRemoteSwiftPackageReference "ton-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/tonkeeper/ton-swift.git"; - requirement = { - kind = exactVersion; - version = 1.0.11; - }; - }; D0DA740B272A6EFC0072BE86 /* XCRemoteSwiftPackageReference "UnicodeURL" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nysander/UnicodeURL.git"; @@ -13050,7 +13014,7 @@ repositoryURL = "https://github.com/horizontalsystems/MarketKit.Swift"; requirement = { kind = exactVersion; - version = 3.0.11; + version = 3.0.12; }; }; D3604E7D28F03C1D0066C366 /* XCRemoteSwiftPackageReference "Chart" */ = { @@ -13292,56 +13256,6 @@ package = D08C93AD2B91E37E00A7D1D5 /* XCRemoteSwiftPackageReference "Hodler" */; productName = Hodler; }; - D0D343C62C6E1ECB008F1D47 /* EventSource */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = EventSource; - }; - D0D343C82C6E1ECB008F1D47 /* StreamURLSessionTransport */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = StreamURLSessionTransport; - }; - D0D343CA2C6E1ECB008F1D47 /* TonAPI */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = TonAPI; - }; - D0D343CC2C6E1ECB008F1D47 /* TonStreamingAPI */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = TonStreamingAPI; - }; - D0D343CE2C6E1EEB008F1D47 /* EventSource */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = EventSource; - }; - D0D343D02C6E1EEB008F1D47 /* StreamURLSessionTransport */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = StreamURLSessionTransport; - }; - D0D343D22C6E1EEB008F1D47 /* TonAPI */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = TonAPI; - }; - D0D343D42C6E1EEB008F1D47 /* TonStreamingAPI */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343C52C6E1E4F008F1D47 /* XCRemoteSwiftPackageReference "ton-api-swift" */; - productName = TonStreamingAPI; - }; - D0D343D72C6E1F83008F1D47 /* TonSwift */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343D62C6E1F83008F1D47 /* XCRemoteSwiftPackageReference "ton-swift" */; - productName = TonSwift; - }; - D0D343D92C6E1F98008F1D47 /* TonSwift */ = { - isa = XCSwiftPackageProductDependency; - package = D0D343D62C6E1F83008F1D47 /* XCRemoteSwiftPackageReference "ton-swift" */; - productName = TonSwift; - }; D0DA740C272A6EFC0072BE86 /* IDNSDK */ = { isa = XCSwiftPackageProductDependency; package = D0DA740B272A6EFC0072BE86 /* XCRemoteSwiftPackageReference "UnicodeURL" */; diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/Contents.json b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/Contents.json new file mode 100644 index 0000000000..e5f0a7399d --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "the-open-network@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "the-open-network@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@2x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@2x.png new file mode 100644 index 0000000000..e131ccf44d Binary files /dev/null and b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@2x.png differ diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@3x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@3x.png new file mode 100644 index 0000000000..416f93e78b Binary files /dev/null and b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Images/the-open-network_jetton_32.imageset/the-open-network@3x.png differ diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift new file mode 100644 index 0000000000..9449b4cafd --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift @@ -0,0 +1,172 @@ +import BigInt +import Combine +import Foundation +import RxSwift +import TonKit +import TonSwift + +class JettonAdapter { + private let tonKit: TonKit.Kit + private let address: TonSwift.Address + private var cancellables = Set() + + private let balanceStateSubject = PublishSubject() + private(set) var balanceState: AdapterState { + didSet { + balanceStateSubject.onNext(balanceState) + } + } + + private let jettonBalanceSubject = PublishSubject() + private(set) var jettonBalance: JettonBalance? { + didSet { + jettonBalanceSubject.onNext(jettonBalance) + } + } + + private let transactionRecordsSubject = PublishSubject<[TonTransactionRecord]>() + + init(tonKit: TonKit.Kit, address: String) throws { + self.tonKit = tonKit + self.address = try TonSwift.Address.parse(address) + + balanceState = Self.adapterState(kitSyncState: tonKit.jettonSyncState) + jettonBalance = tonKit.jettonBalanceMap[self.address] + + tonKit.jettonSyncStatePublisher + .sink { [weak self] in self?.balanceState = Self.adapterState(kitSyncState: $0) } + .store(in: &cancellables) + + tonKit.jettonBalanceMapPublisher + .sink { [weak self] in self?.handle(jettonBalanceMap: $0) } + .store(in: &cancellables) + } + + private func handle(jettonBalanceMap: [TonSwift.Address: JettonBalance]) { + jettonBalance = jettonBalanceMap[address] + } + + private static func adapterState(kitSyncState: TonKit.SyncState) -> AdapterState { + switch kitSyncState { + case .syncing: return .syncing(progress: nil, lastBlockDate: nil) + case .synced: return .synced + case let .notSynced(error): return .notSynced(error: error) + } + } + + private static func amount(jettonBalance: JettonBalance?) -> Decimal { + guard let jettonBalance, let significand = Decimal(string: jettonBalance.balance.description) else { + return 0 + } + + return Decimal(sign: .plus, exponent: -jettonBalance.jetton.decimals, significand: significand) + } + + private static func amount(kitAmount: BigUInt, decimals: Int) -> Decimal { + guard let significand = Decimal(string: kitAmount.description) else { + return 0 + } + + return Decimal(sign: .plus, exponent: -decimals, significand: significand) + } +} + +extension JettonAdapter: IBaseAdapter { + var isMainNet: Bool { + true + } +} + +extension JettonAdapter: IAdapter { + func start() { + // started via TonKitManager + } + + func stop() { + // stopped via TonKitManager + } + + func refresh() { + // refreshed via TonKitManager + } + + var statusInfo: [(String, Any)] { + [] + } + + var debugInfo: String { + "" + } +} + +extension JettonAdapter: IBalanceAdapter { + var balanceStateUpdatedObservable: Observable { + balanceStateSubject.asObservable() + } + + var balanceData: BalanceData { + BalanceData(available: Self.amount(jettonBalance: jettonBalance)) + } + + var balanceDataUpdatedObservable: Observable { + jettonBalanceSubject.map { BalanceData(available: Self.amount(jettonBalance: $0)) } + } +} + +extension JettonAdapter: IDepositAdapter { + var receiveAddress: DepositAddress { + DepositAddress(tonKit.receiveAddress.toString(bounceable: false)) + } +} + +extension JettonAdapter: ISendTonAdapter { + var availableBalance: Decimal { + balanceData.available + } + + func validate(address: String) throws { + _ = try FriendlyAddress(string: address) + } + + func estimateFee(recipient: String, amount: Decimal, comment: String?) async throws -> Decimal { + guard let jettonBalance else { + throw EstimateError.noWalletAddress + } + + let recipient = try FriendlyAddress(string: recipient) + let amount = Decimal(sign: .plus, exponent: jettonBalance.jetton.decimals, significand: amount).rounded(decimal: 0) + + guard let kitAmount = BigUInt(amount.description) else { + throw AmountError.invalidAmount + } + + let kitFee = try await tonKit.estimateFee(jettonWallet: jettonBalance.walletAddress, recipient: recipient, amount: kitAmount, comment: comment) + + return Self.amount(kitAmount: kitFee, decimals: jettonBalance.jetton.decimals) + } + + func send(recipient: String, amount: Decimal, comment: String?) async throws { + guard let jettonBalance else { + throw EstimateError.noWalletAddress + } + + let recipient = try FriendlyAddress(string: recipient) + let amount = Decimal(sign: .plus, exponent: jettonBalance.jetton.decimals, significand: amount).rounded(decimal: 0) + + guard let kitAmount = BigUInt(amount.description) else { + throw AmountError.invalidAmount + } + + try await tonKit.send(jettonWallet: jettonBalance.walletAddress, recipient: recipient, amount: kitAmount, comment: comment) + } +} + +extension JettonAdapter { + enum EstimateError: Error { + case noWalletAddress + } + + enum AmountError: Error { + case invalidAmount + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift index 3c0a95806e..afb7642c42 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift @@ -1,36 +1,20 @@ import BigInt import Combine import Foundation -import HdWalletKit -import HsToolKit -import MarketKit import RxSwift import TonKit import TonSwift -import TweetNacl class TonAdapter { - private static let coinRate: Decimal = 1_000_000_000 - static let bounceableDefault = false + private static let decimals = 9 private let tonKit: TonKit.Kit - private let ownAddress: TonSwift.Address - private let transactionSource: TransactionSource - private let baseToken: Token - private let reachabilityManager = App.shared.reachabilityManager - private let appManager = App.shared.appManager - private var cancellables = Set() - private var adapterStarted = false - private var kitStarted = false - - private let logger: Logger? - - private let adapterStateSubject = PublishSubject() - private(set) var adapterState: AdapterState { + private let balanceStateSubject = PublishSubject() + private(set) var balanceState: AdapterState { didSet { - adapterStateSubject.onNext(adapterState) + balanceStateSubject.onNext(balanceState) } } @@ -43,80 +27,19 @@ class TonAdapter { private let transactionRecordsSubject = PublishSubject<[TonTransactionRecord]>() - init(wallet: Wallet, baseToken: Token) throws { - transactionSource = wallet.transactionSource - self.baseToken = baseToken - -// logger = Logger(minLogLevel: .debug) - logger = App.shared.logger.scoped(with: "TonKit") - - switch wallet.account.type { - case .mnemonic: - guard let seed = wallet.account.type.mnemonicSeed else { - throw AdapterError.unsupportedAccount - } - - let hdWallet = HDWallet(seed: seed, coinType: 607, xPrivKey: 0, curve: .ed25519) - let privateKey = try hdWallet.privateKey(account: 0) - let privateRaw = Data(privateKey.raw.bytes) - let pair = try TweetNacl.NaclSign.KeyPair.keyPair(fromSeed: privateRaw) - let keyPair = KeyPair(publicKey: .init(data: pair.publicKey), - privateKey: .init(data: pair.secretKey)) - - tonKit = try Kit.instance( - type: .full(keyPair), - network: .mainNet, - walletId: wallet.account.id, - apiKey: nil, - logger: logger - ) - - case let .tonAddress(address): - let tonAddress = try TonSwift.Address.parse(address) - tonKit = try Kit.instance( - type: .watch(tonAddress), - network: .mainNet, - walletId: wallet.account.id, - apiKey: nil, - logger: logger - ) - default: - throw AdapterError.unsupportedAccount - } - - ownAddress = tonKit.address + init(tonKit: TonKit.Kit) { + self.tonKit = tonKit - adapterState = Self.adapterState(kitSyncState: tonKit.syncState) - balanceData = BalanceData(available: Self.amount(kitAmount: tonKit.balance)) + balanceState = Self.adapterState(kitSyncState: tonKit.syncState) + balanceData = BalanceData(available: Self.amount(kitAmount: tonKit.account?.balance)) tonKit.syncStatePublisher - .sink { [weak self] syncState in - self?.adapterState = Self.adapterState(kitSyncState: syncState) - } - .store(in: &cancellables) - - tonKit.tonBalancePublisher - .sink { [weak self] balance in - self?.balanceData = BalanceData(available: Self.amount(kitAmount: balance)) - } + .sink { [weak self] in self?.balanceState = Self.adapterState(kitSyncState: $0) } .store(in: &cancellables) - appManager.didEnterBackgroundPublisher - .sink { [weak self] in - self?.stop() - } + tonKit.accountPublisher + .sink { [weak self] in self?.balanceData = BalanceData(available: Self.amount(kitAmount: $0?.balance)) } .store(in: &cancellables) - - appManager.willEnterForegroundPublisher - .sink { [weak self] in - self?.start() - } - .store(in: &cancellables) - } - - private func handle(tonTransactions: [TonKit.FullTransaction]) { - let transactionRecords = tonTransactions.map { transactionRecord(tonTransaction: $0) } - transactionRecordsSubject.onNext(transactionRecords) } private static func adapterState(kitSyncState: TonKit.SyncState) -> AdapterState { @@ -127,70 +50,20 @@ class TonAdapter { } } - static func amount(kitAmount: String) -> Decimal { - Decimal(string: kitAmount).map { amount(kitAmount: $0) } ?? 0 - } - - static func amount(kitAmount: BigUInt) -> Decimal { - amount(kitAmount: kitAmount.toDecimal(decimals: 0) ?? 0) - } - - static func amount(kitAmount: Decimal) -> Decimal { - kitAmount / coinRate - } - - private func transactionRecord(tonTransaction tx: TonKit.FullTransaction) -> TonTransactionRecord { - switch tx.decoration { - case is TonKit.IncomingDecoration: - return TonIncomingTransactionRecord( - source: .init(blockchainType: .ton, meta: nil), - event: tx.event, - feeToken: baseToken, - token: baseToken - ) - - case let decoration as TonKit.OutgoingDecoration: - return TonOutgoingTransactionRecord( - source: .init(blockchainType: .ton, meta: nil), - event: tx.event, - feeToken: baseToken, - token: baseToken, - sentToSelf: decoration.sentToSelf - ) - - default: - return TonTransactionRecord( - source: .init(blockchainType: .ton, meta: nil), - event: tx.event, - feeToken: baseToken - ) - } - } - - private func tagQuery(token _: MarketKit.Token?, filter: TransactionTypeFilter, address: String?) -> TransactionTagQuery { - var type: TransactionTag.TagType? - - switch filter { - case .all: () - case .incoming: type = .incoming - case .outgoing: type = .outgoing - case .swap: type = .swap - case .approve: type = .approve + private static func amount(kitAmount: BigUInt?) -> Decimal { + guard let kitAmount, let significand = Decimal(string: kitAmount.description) else { + return 0 } - return TransactionTagQuery(type: type, protocol: .native, jettonAddress: nil, address: address) + return Decimal(sign: .plus, exponent: -Self.decimals, significand: significand) } - private func startKit() { - logger?.log(level: .debug, message: "TonAdapter, start kit.") - tonKit.start() - kitStarted = true + static func amount(kitAmount: String) -> Decimal { + amount(kitAmount: BigUInt(kitAmount)) } - private func stopKit() { - logger?.log(level: .debug, message: "TonAdapter, stop kit.") - tonKit.stop() - kitStarted = false + static func amount(kitAmount: Int64) -> Decimal { + amount(kitAmount: BigUInt(kitAmount)) } } @@ -202,27 +75,19 @@ extension TonAdapter: IBaseAdapter { extension TonAdapter: IAdapter { func start() { - adapterStarted = true - - if reachabilityManager.isReachable { - startKit() - } + // started via TonKitManager } func stop() { - adapterStarted = false - - if kitStarted { - stopKit() - } + // stopped via TonKitManager } func refresh() { - tonKit.refresh() + // refreshed via TonKitManager } var statusInfo: [(String, Any)] { - [] // tonKit.statusInfo() + [] } var debugInfo: String { @@ -232,116 +97,56 @@ extension TonAdapter: IAdapter { extension TonAdapter: IBalanceAdapter { var balanceStateUpdatedObservable: Observable { - adapterStateSubject + balanceStateSubject.asObservable() } var balanceDataUpdatedObservable: Observable { balanceDataSubject.asObservable() } - - var balanceState: AdapterState { - adapterState - } } extension TonAdapter: IDepositAdapter { var receiveAddress: DepositAddress { - DepositAddress(tonKit.receiveAddress.toString(bounceable: TonAdapter.bounceableDefault)) + DepositAddress(tonKit.receiveAddress.toString(bounceable: false)) } } -extension TonAdapter: ITransactionsAdapter { - var syncing: Bool { - adapterState.syncing +extension TonAdapter: ISendTonAdapter { + var availableBalance: Decimal { + balanceData.available } - var syncingObservable: Observable { - adapterStateSubject.map { _ in () } + func validate(address: String) throws { + _ = try FriendlyAddress(string: address) } - var lastBlockInfo: LastBlockInfo? { - nil - } + func estimateFee(recipient: String, amount: Decimal, comment: String?) async throws -> Decimal { + let recipient = try FriendlyAddress(string: recipient) + let amount = Decimal(sign: .plus, exponent: Self.decimals, significand: amount).rounded(decimal: 0) - var lastBlockUpdatedObservable: Observable { - Observable.empty() - } + guard let kitAmount = BigUInt(amount.description) else { + throw AmountError.invalidAmount + } - var explorerTitle: String { - "tonscan.org" - } + let kitFee = try await tonKit.estimateFee(recipient: recipient, amount: .amount(value: kitAmount), comment: comment) - var additionalTokenQueries: [TokenQuery] { - [] + return Self.amount(kitAmount: kitFee) } - func explorerUrl(transactionHash: String) -> String? { - "https://tonscan.org/tx/\(transactionHash)" - } - - func transactionsObservable(token: Token?, filter: TransactionTypeFilter, address: String?) -> Observable<[TransactionRecord]> { - let address = address.flatMap { try? FriendlyAddress(string: $0) }?.address.toRaw() - - return tonKit.transactionsPublisher(tagQueries: [tagQuery(token: token, filter: filter, address: address)]).asObservable() - .map { [weak self] in - $0.compactMap { self?.transactionRecord(tonTransaction: $0) } - } - } + func send(recipient: String, amount: Decimal, comment: String?) async throws { + let recipient = try FriendlyAddress(string: recipient) + let amount = Decimal(sign: .plus, exponent: Self.decimals, significand: amount).rounded(decimal: 0) - func transactionsSingle(from: TransactionRecord?, token _: Token?, filter: TransactionTypeFilter, address: String?, limit: Int) -> Single<[TransactionRecord]> { - Single.create { [weak self] observer in - guard let self else { - observer(.error(AppError.unknownError)) - return Disposables.create() - } - - Task { [weak self] in - let address = address.flatMap { try? FriendlyAddress(string: $0) }?.address.toRaw() - - let beforeLt = (from as? TonTransactionRecord).map(\.lt) - var tagQueries = [TransactionTagQuery]() - switch filter { - case .all: () - case .incoming: tagQueries.append(.init(type: .incoming, address: address)) - case .outgoing: tagQueries.append(.init(type: .outgoing, address: address)) - default: observer(.success([])) - } - - let txs = (self?.tonKit - .transactions(tagQueries: tagQueries, beforeLt: beforeLt, limit: limit) - .compactMap { self?.transactionRecord(tonTransaction: $0) }) ?? [] - - observer(.success(txs)) - } - - return Disposables.create() + guard let kitAmount = BigUInt(amount.description) else { + throw AmountError.invalidAmount } - } - func rawTransaction(hash _: String) -> String? { - nil + try await tonKit.send(recipient: recipient, amount: .amount(value: kitAmount), comment: comment) } } -extension TonAdapter: ISendTonAdapter { - var availableBalance: Decimal { - balanceData.available - } - - func validate(address: String) throws { - _ = try FriendlyAddress(string: address) - } - - func estimateFee(recipient: String, amount: Decimal, memo: String?) async throws -> Decimal { - let amount = (amount * Self.coinRate).rounded(decimal: 0) - - let kitAmount = try await tonKit.estimateFee(recipient: recipient, amount: amount, comment: memo) - return Self.amount(kitAmount: kitAmount) - } - - func send(recipient: String, amount: Decimal, memo: String?) async throws { - let amount = (amount * Self.coinRate).rounded(decimal: 0) - - try await tonKit.send(recipient: recipient, amount: amount, comment: memo) +extension TonAdapter { + enum AmountError: Error { + case invalidAmount } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonEventConverter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonEventConverter.swift new file mode 100644 index 0000000000..1d1f73da60 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonEventConverter.swift @@ -0,0 +1,101 @@ +import BigInt +import Foundation +import MarketKit +import TonKit +import TonSwift + +class TonEventConverter { + private let address: TonSwift.Address + private let source: TransactionSource + private let baseToken: Token + private let coinManager: CoinManager + + init(address: TonSwift.Address, source: TransactionSource, baseToken: Token, coinManager: CoinManager) { + self.address = address + self.source = source + self.baseToken = baseToken + self.coinManager = coinManager + } + + private func convertAmount(amount: BigUInt, decimals: Int, sign: FloatingPointSign) -> Decimal { + guard let significand = Decimal(string: amount.description), significand != 0 else { + return 0 + } + + return Decimal(sign: sign, exponent: -decimals, significand: significand) + } + + private func tonValue(value: BigUInt, sign: FloatingPointSign) -> TransactionValue { + let amount = convertAmount(amount: value, decimals: baseToken.decimals, sign: sign) + return .coinValue(token: baseToken, value: amount) + } + + private func jettonValue(jetton: Jetton, value: BigUInt, sign: FloatingPointSign) -> TransactionValue { + let query = TokenQuery(blockchainType: .ton, tokenType: .jetton(address: jetton.address.toString(bounceable: true))) + + if let token = try? coinManager.token(query: query) { + let value = convertAmount(amount: value, decimals: token.decimals, sign: sign) + return .coinValue(token: token, value: value) + } else { + let value = convertAmount(amount: value, decimals: jetton.decimals, sign: sign) + return .jettonValue(name: jetton.name, symbol: jetton.symbol, decimals: jetton.decimals, value: value) + } + } + + private func format(address: AccountAddress) -> String { + address.address.toString(bounceable: !address.isWallet) + } + + private func actionType(type: Action.`Type`) -> TonTransactionRecord.Action.`Type` { + switch type { + case let .tonTransfer(action): + if action.sender.address == address { + return .send(value: tonValue(value: action.amount, sign: .minus), to: format(address: action.recipient), sentToSelf: action.recipient.address == address, comment: action.comment) + } else if action.recipient.address == address { + return .receive(value: tonValue(value: action.amount, sign: .plus), from: format(address: action.sender), comment: action.comment) + } else { + return .unsupported(type: "Ton Transfer") + } + case let .jettonTransfer(action): + if action.sender?.address == address, let recipient = action.recipient { + return .send(value: jettonValue(jetton: action.jetton, value: action.amount, sign: .minus), to: format(address: recipient), sentToSelf: action.recipient?.address == address, comment: action.comment) + } else if action.recipient?.address == address, let sender = action.sender { + return .receive(value: jettonValue(jetton: action.jetton, value: action.amount, sign: .plus), from: format(address: sender), comment: action.comment) + } else { + return .unsupported(type: "Jetton Transfer") + } + case let .jettonBurn(action): + return .unsupported(type: "Jetton Burn") + case let .jettonMint(action): + return .unsupported(type: "Jetton Mint") + case let .contractDeploy(action): + return .unsupported(type: "Contract Deploy") + case let .jettonSwap(action): + return .unsupported(type: "Jetton Swap") + case let .smartContract(action): + return .unsupported(type: "Smart Contract") + case let .unknown(rawType): + return .unsupported(type: rawType) + } + } +} + +extension TonEventConverter { + func transactionRecord(event: Event) -> TonTransactionRecord { + let actions = event.actions.map { action in + let status: TransactionStatus + + switch action.status { + case .ok: status = .completed + default: status = .failed + } + + return TonTransactionRecord.Action( + type: actionType(type: action.type), + status: status + ) + } + + return TonTransactionRecord(source: source, event: event, baseToken: baseToken, actions: actions) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift new file mode 100644 index 0000000000..e7162205b6 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift @@ -0,0 +1,129 @@ +import BigInt +import Combine +import Foundation +import MarketKit +import RxSwift +import TonKit +import TonSwift + +class TonTransactionAdapter { + private let tonKit: TonKit.Kit + private let converter: TonEventConverter + private var cancellables = Set() + + private let adapterStateSubject = PublishSubject() + private(set) var adapterState: AdapterState { + didSet { + adapterStateSubject.onNext(adapterState) + } + } + + init(tonKit: TonKit.Kit, source: TransactionSource, baseToken: Token, coinManager: CoinManager) { + self.tonKit = tonKit + converter = TonEventConverter(address: tonKit.receiveAddress, source: source, baseToken: baseToken, coinManager: coinManager) + + adapterState = Self.adapterState(kitSyncState: tonKit.eventSyncState) + + tonKit.eventSyncStatePublisher + .sink { [weak self] in self?.adapterState = Self.adapterState(kitSyncState: $0) } + .store(in: &cancellables) + } + + private func tagQuery(token: MarketKit.Token?, filter: TransactionTypeFilter, address: String?) -> TagQuery { + var type: Tag.`Type`? + var platform: Tag.Platform? + var jettonAddress: TonSwift.Address? + + if let token { + switch token.type { + case .native: + platform = .native + case let .jetton(address): + if let address = try? TonSwift.Address.parse(address) { + platform = .jetton + jettonAddress = address + } + default: () + } + } + + switch filter { + case .all: () + case .incoming: type = .incoming + case .outgoing: type = .outgoing + case .swap: type = .unsupported + case .approve: type = .unsupported + } + + let address = address.flatMap { try? TonSwift.Address.parse($0) } + + return TagQuery(type: type, platform: platform, jettonAddress: jettonAddress, address: address) + } + + private static func adapterState(kitSyncState: TonKit.SyncState) -> AdapterState { + switch kitSyncState { + case .syncing: return .syncing(progress: nil, lastBlockDate: nil) + case .synced: return .synced + case let .notSynced(error): return .notSynced(error: error) + } + } +} + +extension TonTransactionAdapter: ITransactionsAdapter { + var syncing: Bool { + adapterState.syncing + } + + var syncingObservable: Observable { + adapterStateSubject.map { _ in () } + } + + var lastBlockInfo: LastBlockInfo? { + nil + } + + var lastBlockUpdatedObservable: Observable { + Observable.empty() + } + + var explorerTitle: String { + "tonviewer.com" + } + + var additionalTokenQueries: [TokenQuery] { + [] // todo + } + + func explorerUrl(transactionHash: String) -> String? { + "https://tonviewer.com/transaction/\(transactionHash)" + } + + func transactionsObservable(token: MarketKit.Token?, filter: TransactionTypeFilter, address: String?) -> Observable<[TransactionRecord]> { + tonKit.eventPublisher(tagQuery: tagQuery(token: token, filter: filter, address: address)) + .asObservable() + .map { [converter] in + $0.map { converter.transactionRecord(event: $0) } + } + } + + func transactionsSingle(from: TransactionRecord?, token: MarketKit.Token?, filter: TransactionTypeFilter, address: String?, limit: Int) -> Single<[TransactionRecord]> { + let tagQuery = tagQuery(token: token, filter: filter, address: address) + + return Single.create { [tonKit, converter] observer in + Task { [tonKit, converter] in + let beforeLt = (from as? TonTransactionRecord).map(\.lt) + + let events = tonKit.events(tagQuery: tagQuery, beforeLt: beforeLt, limit: limit) + let records = events.map { converter.transactionRecord(event: $0) } + + observer(.success(records)) + } + + return Disposables.create() + } + } + + func rawTransaction(hash _: String) -> String? { + nil + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/App.swift b/UnstoppableWallet/UnstoppableWallet/Core/App.swift index 197c3d42b9..e41a3f45fd 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/App.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/App.swift @@ -74,6 +74,7 @@ class App { let evmLabelManager: EvmLabelManager let binanceKitManager: BinanceKitManager let tronAccountManager: TronAccountManager + let tonKitManager: TonKitManager let restoreSettingsManager: RestoreSettingsManager let predefinedBlockchainService: PredefinedBlockchainService @@ -209,6 +210,8 @@ class App { let tronKitManager = TronKitManager(testNetManager: testNetManager) tronAccountManager = TronAccountManager(accountManager: accountManager, walletManager: walletManager, marketKit: marketKit, tronKitManager: tronKitManager, evmAccountRestoreStateManager: evmAccountRestoreStateManager) + tonKitManager = TonKitManager() + let restoreSettingsStorage = RestoreSettingsStorage(dbPool: dbPool) restoreSettingsManager = RestoreSettingsManager(storage: restoreSettingsStorage) predefinedBlockchainService = PredefinedBlockchainService(restoreSettingsManager: restoreSettingsManager) @@ -258,6 +261,7 @@ class App { binanceKitManager: binanceKitManager, btcBlockchainManager: btcBlockchainManager, tronKitManager: tronKitManager, + tonKitManager: tonKitManager, restoreSettingsManager: restoreSettingsManager, coinManager: coinManager, evmLabelManager: evmLabelManager @@ -267,6 +271,7 @@ class App { walletManager: walletManager, evmBlockchainManager: evmBlockchainManager, tronKitManager: tronKitManager, + tonKitManager: tonKitManager, btcBlockchainManager: btcBlockchainManager ) transactionAdapterManager = TransactionAdapterManager( diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Factories/AdapterFactory.swift b/UnstoppableWallet/UnstoppableWallet/Core/Factories/AdapterFactory.swift index cdcf141188..e062c382b5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Factories/AdapterFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Factories/AdapterFactory.swift @@ -10,11 +10,13 @@ class AdapterFactory { private let binanceKitManager: BinanceKitManager private let btcBlockchainManager: BtcBlockchainManager private let tronKitManager: TronKitManager + private let tonKitManager: TonKitManager private let restoreSettingsManager: RestoreSettingsManager private let coinManager: CoinManager private let evmLabelManager: EvmLabelManager - init(evmBlockchainManager: EvmBlockchainManager, evmSyncSourceManager: EvmSyncSourceManager, binanceKitManager: BinanceKitManager, btcBlockchainManager: BtcBlockchainManager, tronKitManager: TronKitManager, + init(evmBlockchainManager: EvmBlockchainManager, evmSyncSourceManager: EvmSyncSourceManager, binanceKitManager: BinanceKitManager, + btcBlockchainManager: BtcBlockchainManager, tronKitManager: TronKitManager, tonKitManager: TonKitManager, restoreSettingsManager: RestoreSettingsManager, coinManager: CoinManager, evmLabelManager: EvmLabelManager) { self.evmBlockchainManager = evmBlockchainManager @@ -22,6 +24,7 @@ class AdapterFactory { self.binanceKitManager = binanceKitManager self.btcBlockchainManager = btcBlockchainManager self.tronKitManager = tronKitManager + self.tonKitManager = tonKitManager self.restoreSettingsManager = restoreSettingsManager self.coinManager = coinManager self.evmLabelManager = evmLabelManager @@ -93,6 +96,16 @@ extension AdapterFactory { return nil } + func tonTransactionAdapter(transactionSource: TransactionSource) -> ITransactionsAdapter? { + let query = TokenQuery(blockchainType: .ton, tokenType: .native) + + if let tonKit = tonKitManager.tonKit, let baseToken = try? coinManager.token(query: query) { + return TonTransactionAdapter(tonKit: tonKit, source: transactionSource, baseToken: baseToken, coinManager: coinManager) + } + + return nil + } + func adapter(wallet: Wallet) -> IAdapter? { switch (wallet.token.type, wallet.token.blockchain.type) { case (.derived, .bitcoin): @@ -138,11 +151,14 @@ extension AdapterFactory { return trc20Adapter(address: address, wallet: wallet) case (.native, .ton): - let query = TokenQuery(blockchainType: .ton, tokenType: .native) - - if let baseToken = try? coinManager.token(query: query) { - return try? TonAdapter(wallet: wallet, baseToken: baseToken) + if let tonKit = try? tonKitManager.tonKit(account: wallet.account) { + return TonAdapter(tonKit: tonKit) } + case let (.jetton(address), .ton): + do { + let tonKit = try tonKitManager.tonKit(account: wallet.account) + return try JettonAdapter(tonKit: tonKit, address: address) + } catch {} default: () } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/AdapterManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/AdapterManager.swift index d175a7499d..eba1bbc68e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/AdapterManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/AdapterManager.swift @@ -10,6 +10,7 @@ class AdapterManager { private let walletManager: WalletManager private let evmBlockchainManager: EvmBlockchainManager private let tronKitManager: TronKitManager + private let tonKitManager: TonKitManager private let adapterDataReadyRelay = PublishRelay() @@ -18,12 +19,13 @@ class AdapterManager { private var _adapterData = AdapterData(adapterMap: [:], account: nil) init(adapterFactory: AdapterFactory, walletManager: WalletManager, evmBlockchainManager: EvmBlockchainManager, - tronKitManager: TronKitManager, btcBlockchainManager: BtcBlockchainManager) + tronKitManager: TronKitManager, tonKitManager: TonKitManager, btcBlockchainManager: BtcBlockchainManager) { self.adapterFactory = adapterFactory self.walletManager = walletManager self.evmBlockchainManager = evmBlockchainManager self.tronKitManager = tronKitManager + self.tonKitManager = tonKitManager walletManager.activeWalletDataUpdatedObservable .observeOn(SerialDispatchQueueScheduler(qos: .userInitiated)) @@ -146,6 +148,7 @@ extension AdapterManager { for blockchain in self.evmBlockchainManager.allBlockchains { self.evmBlockchainManager.evmKitManager(blockchainType: blockchain.type).evmKitWrapper?.evmKit.refresh() } + var binanceKitUpdated = false for (wallet, adapter) in self._adapterData.adapterMap { @@ -161,6 +164,7 @@ extension AdapterManager { } self.tronKitManager.tronKitWrapper?.tronKit.refresh() + self.tonKitManager.tonKit?.refresh() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/TonKitManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/TonKitManager.swift new file mode 100644 index 0000000000..5a50d8e498 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/TonKitManager.swift @@ -0,0 +1,65 @@ +import Foundation +import HdWalletKit +import TonKit +import TonSwift +import TweetNacl + +class TonKitManager { + private weak var _tonKit: Kit? + private var currentAccount: Account? + + private let queue = DispatchQueue(label: "\(AppConfig.label).ton-kit-manager", qos: .userInitiated) + + private func _tonKit(account: Account) throws -> Kit { + if let _tonKit, let currentAccount, currentAccount == account { + return _tonKit + } + + let type: Kit.WalletType + + switch account.type { + case .mnemonic: + guard let seed = account.type.mnemonicSeed else { + throw AdapterError.unsupportedAccount + } + + let hdWallet = HDWallet(seed: seed, coinType: 607, xPrivKey: 0, curve: .ed25519) + let privateKey = try hdWallet.privateKey(account: 0) + let privateRaw = Data(privateKey.raw.bytes) + let pair = try TweetNacl.NaclSign.KeyPair.keyPair(fromSeed: privateRaw) + let keyPair = KeyPair(publicKey: .init(data: pair.publicKey), privateKey: .init(data: pair.secretKey)) + + type = .full(keyPair) + case let .tonAddress(address): + let tonAddress = try TonSwift.Address.parse(address) + type = .watch(tonAddress) + default: + throw AdapterError.unsupportedAccount + } + + let tonKit = try Kit.instance( + type: type, + walletVersion: .v4, + network: .mainNet, + walletId: account.id, + minLogLevel: .error + ) + + tonKit.refresh() + + _tonKit = tonKit + currentAccount = account + + return tonKit + } +} + +extension TonKitManager { + var tonKit: Kit? { + queue.sync { _tonKit } + } + + func tonKit(account: Account) throws -> Kit { + try queue.sync { try _tonKit(account: account) } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/TransactionAdapterManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/TransactionAdapterManager.swift index 6771407feb..cfada05cb3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/TransactionAdapterManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/TransactionAdapterManager.swift @@ -43,6 +43,8 @@ class TransactionAdapterManager { transactionsAdapter = adapterFactory.evmTransactionsAdapter(transactionSource: source) } else if source.blockchainType == .tron { transactionsAdapter = adapterFactory.tronTransactionsAdapter(transactionSource: source) + } else if source.blockchainType == .ton { + transactionsAdapter = adapterFactory.tonTransactionAdapter(transactionSource: source) } else { transactionsAdapter = adapter as? ITransactionsAdapter } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift b/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift index 801f8d65e2..8a6f666a83 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift @@ -103,8 +103,8 @@ protocol ISendTronAdapter { protocol ISendTonAdapter { var availableBalance: Decimal { get } func validate(address: String) throws - func estimateFee(recipient: String, amount: Decimal, memo: String?) async throws -> Decimal - func send(recipient: String, amount: Decimal, memo: String?) async throws + func estimateFee(recipient: String, amount: Decimal, comment: String?) async throws -> Decimal + func send(recipient: String, amount: Decimal, comment: String?) async throws } protocol IErc20Adapter { diff --git a/UnstoppableWallet/UnstoppableWallet/Extensions/Token.swift b/UnstoppableWallet/UnstoppableWallet/Extensions/Token.swift index 9b6f23d28d..6ba2f8f1fc 100644 --- a/UnstoppableWallet/UnstoppableWallet/Extensions/Token.swift +++ b/UnstoppableWallet/UnstoppableWallet/Extensions/Token.swift @@ -18,6 +18,8 @@ extension Token { case .tron: return "TRC20" default: return blockchain.name } + case .jetton: + return "JETTON" case .bep2: return "BEP2" default: diff --git a/UnstoppableWallet/UnstoppableWallet/Extensions/TokenType.swift b/UnstoppableWallet/UnstoppableWallet/Extensions/TokenType.swift index 0cacf7c5de..a8f9e5378c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Extensions/TokenType.swift +++ b/UnstoppableWallet/UnstoppableWallet/Extensions/TokenType.swift @@ -16,6 +16,7 @@ extension TokenType { case .eip20: return .eip20 case .bep2: return .bep2 case .spl: return .spl + case .jetton: return .jetton case .unsupported: return .unsupported } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/AccountType.swift b/UnstoppableWallet/UnstoppableWallet/Models/AccountType.swift index c1bf0be88d..4ef4a220c8 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/AccountType.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/AccountType.swift @@ -83,7 +83,7 @@ enum AccountType { case (.arbitrumOne, .native), (.arbitrumOne, .eip20): return true case (.optimism, .native), (.optimism, .eip20): return true case (.tron, .native), (.tron, .eip20): return true - case (.ton, .native): return true + case (.ton, .native), (.ton, .jetton): return true default: return false } case let .hdExtendedKey(key): @@ -122,7 +122,7 @@ enum AccountType { } case .tonAddress: switch (token.blockchainType, token.type) { - case (.ton, .native): return true + case (.ton, .native), (.ton, .jetton): return true default: return false } case let .btcAddress(_, blockchainType, tokenType): diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TokenProtocol.swift b/UnstoppableWallet/UnstoppableWallet/Models/TokenProtocol.swift index 0629f2fd0d..aec6dc8a83 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TokenProtocol.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TokenProtocol.swift @@ -3,5 +3,6 @@ enum TokenProtocol { case eip20 case bep2 case spl + case jetton case unsupported } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Evm/EvmTransactionRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Evm/EvmTransactionRecord.swift index 3a3263c5ad..c5dadc61bb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Evm/EvmTransactionRecord.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Evm/EvmTransactionRecord.swift @@ -62,8 +62,8 @@ class EvmTransactionRecord: TransactionRecord { resultValue = .tokenValue(tokenName: tokenName, tokenCode: tokenCode, tokenDecimals: tokenDecimals, value: totalValue) case let .nftValue(nftUid, _, tokenName, tokenSymbol): resultValue = .nftValue(nftUid: nftUid, value: totalValue, tokenName: tokenName, tokenSymbol: tokenSymbol) - case let .rawValue(value): - resultValue = .rawValue(value: value) + default: + resultValue = value } if totalValue > 0 { diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonIncomingTransactionRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonIncomingTransactionRecord.swift deleted file mode 100644 index a7e58ef1a7..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonIncomingTransactionRecord.swift +++ /dev/null @@ -1,28 +0,0 @@ -import BigInt -import Foundation -import MarketKit -import TonKit -import TonSwift - -class TonIncomingTransactionRecord: TonTransactionRecord { - let transfer: Transfer? - - init(source: TransactionSource, event: AccountEvent, feeToken: Token, token: Token) { - transfer = event - .actions - .compactMap { $0 as? TonTransfer } - .first - .map { transfer in - Transfer( - address: transfer.recipient.address.toString(bounceable: TonAdapter.bounceableDefault), - value: .coinValue(token: token, value: TonAdapter.amount(kitAmount: Decimal(transfer.amount))) - ) - } - - super.init(source: source, event: event, feeToken: feeToken) - } - - override var mainValue: TransactionValue? { - transfer?.value - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonOutgoingTransactionRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonOutgoingTransactionRecord.swift deleted file mode 100644 index 04b2f99918..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonOutgoingTransactionRecord.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import MarketKit -import TonKit - -class TonOutgoingTransactionRecord: TonTransactionRecord { - let transfers: [Transfer] - let totalValue: TransactionValue - let sentToSelf: Bool - - init(source: TransactionSource, event: AccountEvent, feeToken: Token, token: Token, sentToSelf: Bool) { - var totalAmount: Decimal = 0 - - transfers = event.actions.compactMap { transfer in - guard let transfer = transfer as? TonTransfer else { - return nil - } - let tonValue = TonAdapter.amount(kitAmount: Decimal(transfer.amount)) - var value: Decimal = 0 - if !tonValue.isZero { - value = Decimal(sign: .minus, exponent: tonValue.exponent, significand: tonValue.significand) - totalAmount += value - } - - return Transfer( - address: transfer.recipient.address.toString(bounceable: TonAdapter.bounceableDefault), - value: .coinValue(token: token, value: value) - ) - } - - totalValue = .coinValue(token: token, value: totalAmount) - self.sentToSelf = sentToSelf - - super.init(source: source, event: event, feeToken: feeToken) - } - - override var mainValue: TransactionValue? { - totalValue - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonTransactionRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonTransactionRecord.swift index d3966e1b2c..3866ce637b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonTransactionRecord.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Ton/TonTransactionRecord.swift @@ -1,21 +1,24 @@ import Foundation import MarketKit import TonKit +import TonSwift class TonTransactionRecord: TransactionRecord { - let fee: TransactionValue? let lt: Int64 - let memo: String? + let inProgress: Bool + let fee: TransactionValue? + let actions: [Action] - init(source: TransactionSource, event: AccountEvent, feeToken: Token) { - fee = .coinValue(token: feeToken, value: TonAdapter.amount(kitAmount: Decimal(event.fee))) + init(source: TransactionSource, event: Event, baseToken: Token, actions: [Action]) { lt = event.lt - memo = event.actions.compactMap { ($0 as? TonTransfer)?.comment }.first + inProgress = event.inProgress + fee = .coinValue(token: baseToken, value: TonAdapter.amount(kitAmount: abs(event.extra))) + self.actions = actions super.init( source: source, - uid: event.eventId, - transactionHash: event.eventId, + uid: event.id, + transactionHash: event.id, transactionIndex: 0, blockHeight: nil, confirmationsThreshold: nil, @@ -25,13 +28,31 @@ class TonTransactionRecord: TransactionRecord { } override func status(lastBlockHeight _: Int?) -> TransactionStatus { - .completed + inProgress ? .pending : .completed + } + + override var mainValue: TransactionValue? { + if actions.count == 1, let action = actions.first { + switch action.type { + case let .send(value, _, _, _): return value + case let .receive(value, _, _): return value + case .unsupported: return nil + } + } + + return nil } } extension TonTransactionRecord { - struct Transfer { - let address: String - let value: TransactionValue + struct Action { + let type: `Type` + let status: TransactionStatus + + enum `Type` { + case send(value: TransactionValue, to: String, sentToSelf: Bool, comment: String?) + case receive(value: TransactionValue, from: String, comment: String?) + case unsupported(type: String) + } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Tron/TronTransactionRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Tron/TronTransactionRecord.swift index 7c3f237f8f..a8c1fc88cc 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Tron/TronTransactionRecord.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TransactionRecords/Tron/TronTransactionRecord.swift @@ -64,8 +64,8 @@ class TronTransactionRecord: TransactionRecord { resultValue = .tokenValue(tokenName: tokenName, tokenCode: tokenCode, tokenDecimals: tokenDecimals, value: totalValue) case let .nftValue(nftUid, _, tokenName, tokenSymbol): resultValue = .nftValue(nftUid: nftUid, value: totalValue, tokenName: tokenName, tokenSymbol: tokenSymbol) - case let .rawValue(value): - resultValue = .rawValue(value: value) + default: + resultValue = value } if totalValue > 0 { diff --git a/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift b/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift index c911494ff9..28cee1c065 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/TransactionValue.swift @@ -5,6 +5,7 @@ import MarketKit enum TransactionValue { case coinValue(token: Token, value: Decimal) case tokenValue(tokenName: String, tokenCode: String, tokenDecimals: Int, value: Decimal) + case jettonValue(name: String, symbol: String, decimals: Int, value: Decimal) case nftValue(nftUid: NftUid, value: Decimal, tokenName: String?, tokenSymbol: String?) case rawValue(value: BigUInt) @@ -12,6 +13,7 @@ enum TransactionValue { switch self { case let .coinValue(token, _): return token.coin.name case let .tokenValue(tokenName, _, _, _): return tokenName + case let .jettonValue(name, _, _, _): return name case let .nftValue(nftUid, _, tokenName, _): return tokenName.map { "\($0) #\(nftUid.tokenId)" } ?? "#\(nftUid.tokenId)" case .rawValue: return "" } @@ -21,6 +23,7 @@ enum TransactionValue { switch self { case let .coinValue(token, _): return token.coin.code case let .tokenValue(_, tokenCode, _, _): return tokenCode + case let .jettonValue(_, symbol, _, _): return symbol case let .nftValue(_, _, _, tokenSymbol): return tokenSymbol ?? "NFT" case .rawValue: return "" } @@ -44,6 +47,7 @@ enum TransactionValue { switch self { case let .coinValue(token, _): return token.type.tokenProtocol case .tokenValue: return .eip20 + case .jettonValue: return .jetton case .nftValue: return nil case .rawValue: return nil } @@ -60,6 +64,7 @@ enum TransactionValue { switch self { case let .coinValue(_, value): return value case let .tokenValue(_, _, _, value): return value + case let .jettonValue(_, _, _, value): return value case let .nftValue(_, value, _, _): return value case .rawValue: return nil } @@ -69,6 +74,7 @@ enum TransactionValue { switch self { case let .coinValue(_, value): return value == 0 case let .tokenValue(_, _, _, value): return value == 0 + case let .jettonValue(_, _, _, value): return value == 0 case let .nftValue(_, value, _, _): return value == 0 case let .rawValue(value): return value == 0 } @@ -78,6 +84,7 @@ enum TransactionValue { switch self { case let .coinValue(token, value): return value.isMaxValue(decimals: token.decimals) case let .tokenValue(_, _, tokenDecimals, value): return value.isMaxValue(decimals: tokenDecimals) + case let .jettonValue(_, _, decimals, value): return value.isMaxValue(decimals: decimals) default: return false } } @@ -88,6 +95,8 @@ enum TransactionValue { return ValueFormatter.instance.formatFull(value: value, decimalCount: token.decimals, symbol: token.coin.code, signType: signType) case let .tokenValue(_, tokenCode, tokenDecimals, value): return ValueFormatter.instance.formatFull(value: value, decimalCount: tokenDecimals, symbol: tokenCode, signType: signType) + case let .jettonValue(_, symbol, decimals, value): + return ValueFormatter.instance.formatFull(value: value, decimalCount: decimals, symbol: symbol, signType: signType) case let .nftValue(_, value, _, tokenSymbol): return "\(value.sign == .plus ? "+" : "")\(value) \(tokenSymbol ?? "NFT")" case .rawValue: @@ -101,6 +110,8 @@ enum TransactionValue { return ValueFormatter.instance.formatShort(value: value, decimalCount: token.decimals, symbol: token.coin.code, signType: signType) case let .tokenValue(_, tokenCode, tokenDecimals, value): return ValueFormatter.instance.formatShort(value: value, decimalCount: tokenDecimals, symbol: tokenCode, signType: signType) + case let .jettonValue(_, symbol, decimals, value): + return ValueFormatter.instance.formatShort(value: value, decimalCount: decimals, symbol: symbol, signType: signType) case let .nftValue(_, value, _, tokenSymbol): return "\(value.sign == .plus ? "+" : "")\(value) \(tokenSymbol ?? "NFT")" case .rawValue: @@ -114,6 +125,7 @@ extension TransactionValue: Equatable { switch (lhs, rhs) { case let (.coinValue(lhsToken, lhsValue), .coinValue(rhsToken, rhsValue)): return lhsToken == rhsToken && lhsValue == rhsValue case let (.tokenValue(lhsTokenName, lhsTokenCode, lhsTokenDecimals, lhsValue), .tokenValue(rhsTokenName, rhsTokenCode, rhsTokenDecimals, rhsValue)): return lhsTokenName == rhsTokenName && lhsTokenCode == rhsTokenCode && lhsTokenDecimals == rhsTokenDecimals && lhsValue == rhsValue + case let (.jettonValue(lhsName, lhsSymbol, lhsDecimals, lhsValue), .tokenValue(rhsName, rhsSymbol, rhsDecimals, rhsValue)): return lhsName == rhsName && lhsSymbol == rhsSymbol && lhsDecimals == rhsDecimals && lhsValue == rhsValue case let (.nftValue(lhsNftUid, lhsValue, _, _), .nftValue(rhsNftUid, rhsValue, _, _)): return lhsNftUid == rhsNftUid && lhsValue == rhsValue case let (.rawValue(lhsValue), .rawValue(rhsValue)): return lhsValue == rhsValue default: return false diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Overview/CoinOverviewView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Overview/CoinOverviewView.swift index 00ccbc0c96..a2b7929c8f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Overview/CoinOverviewView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Overview/CoinOverviewView.swift @@ -416,6 +416,7 @@ struct CoinOverviewView: View { case let .eip20(address): return address.shortened case let .bep2(symbol): return symbol case let .spl(address): return address.shortened + case let .jetton(address): return address.shortened case let .unsupported(_, reference): return reference?.shortened } } @@ -425,6 +426,7 @@ struct CoinOverviewView: View { case let .eip20(address): return address case let .bep2(symbol): return symbol case let .spl(address): return address + case let .jetton(address): return address case let .unsupported(_, reference): return reference default: return nil } @@ -435,6 +437,7 @@ struct CoinOverviewView: View { case let .eip20(address): return token.blockchain.explorerUrl(reference: address) case let .bep2(symbol): return token.blockchain.explorerUrl(reference: symbol) case let .spl(address): return token.blockchain.explorerUrl(reference: address) + case let .jetton(address): return token.blockchain.explorerUrl(reference: address) case let .unsupported(_, reference): return token.blockchain.explorerUrl(reference: reference) default: return nil } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/MultiSwap/MultiSwapSendHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/MultiSwap/MultiSwapSendHandler.swift index ece27b8059..8a70001001 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/MultiSwap/MultiSwapSendHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/MultiSwap/MultiSwapSendHandler.swift @@ -165,7 +165,7 @@ extension MultiSwapSendHandler { switch tokenIn.type { case .native, .derived, .addressType: baseToken = tokenIn - case .eip20, .bep2, .spl: + case .eip20, .bep2, .spl, .jetton: baseToken = try? App.shared.marketKit.token(query: TokenQuery(blockchainType: tokenIn.blockchainType, tokenType: .native)) case .unsupported: baseToken = nil diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift index a21d2ffdcc..f742f0596a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift @@ -131,7 +131,7 @@ class SendTonService { Task { [weak self, adapter] in do { - let fee = try await adapter.estimateFee(recipient: data.0.raw, amount: data.1, memo: data.2) + let fee = try await adapter.estimateFee(recipient: data.0.raw, amount: data.1, comment: data.2) self?.feeState = .completed(fee) self?.availableBalance = .completed(max(0, adapter.availableBalance - fee)) } catch { @@ -160,7 +160,7 @@ extension SendTonService: ISendService { return Single.create { [adapter] observer in let task = Task { [adapter] in do { - try await adapter.send(recipient: data.0.raw, amount: data.1, memo: data.2) + try await adapter.send(recipient: data.0.raw, amount: data.1, comment: data.2) observer(.success(())) } catch { observer(.error(error)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift index 9f05e2aa9f..caa17bfad1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift @@ -149,7 +149,7 @@ extension TonSendHandler: ISendHandler { let tonBalance = adapter.availableBalance do { - let _fee = try await adapter.estimateFee(recipient: address, amount: amount, memo: memo) + let _fee = try await adapter.estimateFee(recipient: address, amount: amount, comment: memo) var totalAmount = Decimal.zero var sentAmount = amount @@ -197,7 +197,7 @@ extension TonSendHandler: ISendHandler { _ = try await adapter.send( recipient: address, amount: amount, - memo: memo + comment: memo ) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift index 9846a2ad1c..9f7b61087a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift @@ -77,12 +77,14 @@ class TransactionInfoService { tokens.append(tx.fee.token) tokens.append(tx.value.token) - case let tx as TonIncomingTransactionRecord: - tokens.append(tx.transfer?.value.token) - case let tx as TonOutgoingTransactionRecord: - tokens.append(tx.fee?.token) - tx.transfers.forEach { tokens.append($0.value.token) } case let tx as TonTransactionRecord: + for action in tx.actions { + switch action.type { + case let .send(value, _, _, _): tokens.append(value.token) + case let .receive(value, _, _): tokens.append(value.token) + case .unsupported: () + } + } tokens.append(tx.fee?.token) default: () diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift index 44bb876920..2c423fff0f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift @@ -137,7 +137,7 @@ class TransactionInfoViewItemFactory { } } - private func sendSection(source: TransactionSource, transactionValue: TransactionValue, to: String?, rates: [Coin: CurrencyValue], nftMetadata: [NftUid: NftAssetBriefMetadata] = [:], sentToSelf: Bool = false, balanceHidden: Bool) -> [TransactionInfoModule.ViewItem] { + private func sendSection(source: TransactionSource, transactionValue: TransactionValue, to: String?, rates: [Coin: CurrencyValue], nftMetadata: [NftUid: NftAssetBriefMetadata] = [:], sentToSelf: Bool = false, memo: String? = nil, status: TransactionStatus? = nil, balanceHidden: Bool) -> [TransactionInfoModule.ViewItem] { var viewItems = [TransactionInfoModule.ViewItem]() let burn = to == zeroAddress @@ -196,6 +196,14 @@ class TransactionInfoViewItemFactory { viewItems.append(rateViewItem) } + if let memo { + viewItems.append(.memo(text: memo)) + } + + if let status { + viewItems.append(.status(status: status)) + } + return viewItems } @@ -207,7 +215,7 @@ class TransactionInfoViewItemFactory { return condition ? trueType : (falseType ?? trueType) } - private func receiveSection(source: TransactionSource, transactionValue: TransactionValue, from: String?, rates: [Coin: CurrencyValue], nftMetadata: [NftUid: NftAssetBriefMetadata] = [:], balanceHidden: Bool) -> [TransactionInfoModule.ViewItem] { + private func receiveSection(source: TransactionSource, transactionValue: TransactionValue, from: String?, rates: [Coin: CurrencyValue], nftMetadata: [NftUid: NftAssetBriefMetadata] = [:], memo: String? = nil, status: TransactionStatus? = nil, balanceHidden: Bool) -> [TransactionInfoModule.ViewItem] { var viewItems = [TransactionInfoModule.ViewItem]() let mint = from == zeroAddress @@ -265,6 +273,14 @@ class TransactionInfoViewItemFactory { viewItems.append(rateViewItem) } + if let memo { + viewItems.append(.memo(text: memo)) + } + + if let status { + viewItems.append(.status(status: status)) + } + return viewItems } @@ -549,32 +565,20 @@ class TransactionInfoViewItemFactory { feeViewItem = .fee(title: "tx_info.fee".localized, value: feeString(transactionValue: record.fee, rate: _rate(record.fee))) - case let record as TonIncomingTransactionRecord: - if let transfer = record.transfer { - sections.append(.init(receiveSection(source: record.source, transactionValue: transfer.value, from: transfer.address, rates: item.rates, balanceHidden: balanceHidden))) - } - - if let memo = record.memo, !memo.isEmpty { - sections.append(.init([.memo(text: memo)])) - } - case let record as TonOutgoingTransactionRecord: - for transfer in record.transfers { - sections.append(.init(sendSection(source: record.source, transactionValue: transfer.value, to: transfer.address, rates: item.rates, sentToSelf: record.sentToSelf, balanceHidden: balanceHidden))) - } - - if record.sentToSelf { - sections.append(.init([.sentToSelf])) - } - - if let memo = record.memo, !memo.isEmpty { - sections.append(.init([.memo(text: memo)])) - } - - feeViewItem = record.fee.map { .fee(title: "tx_info.fee".localized, value: feeString(transactionValue: $0, rate: _rate($0))) } - case let record as TonTransactionRecord: - if let memo = record.memo, !memo.isEmpty { - sections.append(.init([.memo(text: memo)])) + for action in record.actions { + switch action.type { + case let .send(value, to, sentToSelf, comment): + sections.append(.init(sendSection(source: record.source, transactionValue: value, to: to, rates: item.rates, sentToSelf: sentToSelf, memo: comment, status: action.status, balanceHidden: balanceHidden))) + + if sentToSelf { + sections.append(.init([.sentToSelf])) + } + case let .receive(value, from, comment): + sections.append(.init(receiveSection(source: record.source, transactionValue: value, from: from, rates: item.rates, memo: comment, status: action.status, balanceHidden: balanceHidden))) + case let .unsupported(type): + sections.append(.init([.fee(title: "Action", value: type)])) + } } feeViewItem = record.fee.map { .fee(title: "tx_info.fee".localized, value: feeString(transactionValue: $0, rate: _rate($0))) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/Pool/PoolGroupFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/Pool/PoolGroupFactory.swift index a826d2d205..759fc0849a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/Pool/PoolGroupFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/Pool/PoolGroupFactory.swift @@ -16,7 +16,7 @@ class PoolGroupFactory { } let poolSource: PoolSource - if App.shared.evmBlockchainManager.allBlockchains.contains(where: { $0 == wallet.token.blockchain }) || wallet.token.blockchainType == .tron { + if App.shared.evmBlockchainManager.allBlockchains.contains(where: { $0 == wallet.token.blockchain }) || wallet.token.blockchainType == .tron || wallet.token.blockchainType == .ton { poolSource = PoolSource( token: nil, blockchainType: wallet.token.blockchainType, @@ -49,7 +49,7 @@ class PoolGroupFactory { if contact != nil, address == nil { return [] } - if App.shared.evmBlockchainManager.allBlockchains.contains(where: { $0.type == blockchainType }) || blockchainType == .tron { + if App.shared.evmBlockchainManager.allBlockchains.contains(where: { $0.type == blockchainType }) || blockchainType == .tron || blockchainType == .ton { let poolSource = PoolSource( token: nil, blockchainType: blockchainType, diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift index f317939ec6..6e62367f2e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Transactions/TransactionsViewItemFactory.swift @@ -396,50 +396,40 @@ class TransactionsViewItemFactory { title = record.transaction.contract?.label ?? "transactions.unknown_transaction.title".localized subTitle = "transactions.unknown_transaction.description".localized() - case let record as TonIncomingTransactionRecord: - title = "transactions.receive".localized - if let transfer = record.transfer { - iconType = singleValueIconType(source: record.source, value: transfer.value) - subTitle = "transactions.from".localized(mapped(address: transfer.address, blockchainType: item.record.source.blockchainType)) - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: transfer.value), type: type(value: transfer.value, .incoming)) - } else { - iconType = .localIcon(imageName: item.record.source.blockchainType.iconPlain32) - subTitle = "" - } - - if let currencyValue = item.currencyValue { - secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) - } - - case let record as TonOutgoingTransactionRecord: - title = "transactions.send".localized - - if !record.transfers.isEmpty { - iconType = singleValueIconType(source: record.source, value: record.transfers[0].value) - - if record.transfers.count == 1 { - subTitle = "transactions.to".localized(mapped(address: record.transfers[0].address, blockchainType: item.record.source.blockchainType)) - } else { - subTitle = "transactions.multiple".localized + case let record as TonTransactionRecord: + if record.actions.count == 1, let action = record.actions.first { + switch action.type { + case let .send(value, to, _sentToSelf, _): + iconType = singleValueIconType(source: record.source, value: value) + title = "transactions.send".localized + subTitle = "transactions.to".localized(mapped(address: to, blockchainType: item.record.source.blockchainType)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: value, signType: _sentToSelf ? .never : .always), type: type(value: value, condition: _sentToSelf, .neutral, .outgoing)) + + if let currencyValue = item.currencyValue { + secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) + } + + sentToSelf = _sentToSelf + case let .receive(value, from, _): + iconType = singleValueIconType(source: record.source, value: value) + title = "transactions.receive".localized + subTitle = "transactions.from".localized(mapped(address: from, blockchainType: item.record.source.blockchainType)) + primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: value), type: type(value: value, .incoming)) + + if let currencyValue = item.currencyValue { + secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) + } + case let .unsupported(type): + iconType = .localIcon(imageName: item.record.source.blockchainType.iconPlain32) + title = "transactions.ton_transaction.title".localized + subTitle = type } - - primaryValue = BaseTransactionsViewModel.Value(text: coinString(from: record.totalValue, signType: record.sentToSelf ? .never : .always), type: type(value: record.totalValue, condition: record.sentToSelf, .neutral, .outgoing)) } else { iconType = .localIcon(imageName: item.record.source.blockchainType.iconPlain32) - subTitle = "" - } - - if let currencyValue = item.currencyValue { - secondaryValue = BaseTransactionsViewModel.Value(text: currencyString(from: currencyValue), type: .secondary) + title = "transactions.ton_transaction.title".localized + subTitle = "transactions.multiple".localized } - sentToSelf = record.sentToSelf - - case is TonTransactionRecord: - iconType = .localIcon(imageName: item.record.source.blockchainType.iconPlain32) - title = "transactions.unknown_transaction.title".localized - subTitle = "transactions.unknown_transaction.description".localized() - default: iconType = .localIcon(imageName: item.record.source.blockchainType.iconPlain32) title = "transactions.unknown_transaction.title".localized diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 88afe0aa3d..cce72a6e2f 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -1245,6 +1245,8 @@ "transactions.unknown_transaction.title" = "Unknown Transaction"; "transactions.unknown_transaction.description" = "Transaction can not be parsed"; +"transactions.ton_transaction.title" = "Ton Transaction"; + // Transaction Info "tx_info.title" = "Transaction Info";