diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 83267b73ec..6649d2a5c3 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -442,7 +442,6 @@ 11B35503B3E84FEFCDF1AFED /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E7E7A5DBB09A2A5197D /* ThemeView.swift */; }; 11B3550424326606B055D7E5 /* AboutModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353E80D544DAF20B12B56 /* AboutModule.swift */; }; 11B35504619FDE878D865781 /* MultiSwapMainField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D0D43137223A01FC2DA /* MultiSwapMainField.swift */; }; - 11B35504EF11FA59D2A358BE /* SendTonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F5835A746165C520333 /* SendTonViewController.swift */; }; 11B3550548CB49D32EAC1DF5 /* WatchlistWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B0879F715C0777919AA /* WatchlistWidget.swift */; }; 11B3550846F2DD60D3778FE5 /* EvmSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B0F9BC5C4CDBC5B041D /* EvmSendHandler.swift */; }; 11B35508E26446AC692EBAEF /* RecipientAndSlippageMultiSwapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3599833F166185872A3AC /* RecipientAndSlippageMultiSwapSettingsView.swift */; }; @@ -549,7 +548,6 @@ 11B356476D5E88F21C297B52 /* ManageAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A38C734DF3157C84678 /* ManageAccountViewController.swift */; }; 11B356485CB62582F79D4877 /* CheckboxCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35216E1F4300730E08C5D /* CheckboxCell.swift */; }; 11B3564C551C6C76ECCE387D /* SendEvmViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F6B511DA5E0C60ED156 /* SendEvmViewModel.swift */; }; - 11B3564E05702B4253453F23 /* SendTonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ECB140E2565C55165A0 /* SendTonFactory.swift */; }; 11B3564FBC180A0E6D30BCFA /* TransactionsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35828C8D50D0A5B915B2A /* TransactionsModule.swift */; }; 11B3565070D890657E004402 /* ManageWalletsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CBBFEC11CAE6FDBCFFA /* ManageWalletsModule.swift */; }; 11B356509E6083B971F37E0F /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE8A07A2ADE2F8D0012DE7F /* Currency.swift */; }; @@ -703,7 +701,6 @@ 11B3581F4D975FC21B9A25F2 /* BtcBlockchainSettingsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3589893A8995F86F08B1C /* BtcBlockchainSettingsModule.swift */; }; 11B3581F7CCCEE9F956266E8 /* InputStateWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B18D0C02E331540538B /* InputStateWrapperView.swift */; }; 11B35828C42630241AE8D0E0 /* BackupVerifyWordsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FA70D9570CB2708E1CA /* BackupVerifyWordsService.swift */; }; - 11B3582C5AB8B2BF15962AE7 /* SendTonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F5835A746165C520333 /* SendTonViewController.swift */; }; 11B3582D43032F1F2DAEE0E8 /* BalanceHiddenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352FBA1B29357E0120055 /* BalanceHiddenManager.swift */; }; 11B35830E8C818E413DEAAFC /* RecipientAndSlippageMultiSwapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3599833F166185872A3AC /* RecipientAndSlippageMultiSwapSettingsView.swift */; }; 11B35831AF48B09CE3349E5C /* BalanceCoinIconHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3560C1FC3F73833FA4439 /* BalanceCoinIconHolder.swift */; }; @@ -758,7 +755,6 @@ 11B358AA46441AF0A7DCAA89 /* NftActivityModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35252F90F25774BDD2CB3 /* NftActivityModule.swift */; }; 11B358AE1CCD292DF2D2AC42 /* PrivateKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A9DB4112F41D7FCAC12 /* PrivateKeysViewModel.swift */; }; 11B358AE5241256C9AAFB588 /* SyncMode_v_0_24.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CB98A27269A510F40EE /* SyncMode_v_0_24.swift */; }; - 11B358AFE8EC87CA1DEB4C22 /* SendTonService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A296A67CE158347A785 /* SendTonService.swift */; }; 11B358B004B48988A1F6D888 /* AppUnlockViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BC07CC9E523971ED20E /* AppUnlockViewModel.swift */; }; 11B358B0576F63BE43947DD5 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35614C6E244926AF48701 /* Account.swift */; }; 11B358B0A260AC250BFE65DE /* CexDepositNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35799B0DCCF655F0766BF /* CexDepositNetwork.swift */; }; @@ -837,7 +833,6 @@ 11B3597E4CCEF19A8E4D1222 /* BottomMultiSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351AF16B02C80A859D535 /* BottomMultiSelectorViewController.swift */; }; 11B3598300402F09E04EFCCE /* CoinAnalyticsIssuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35466E6DC969B551B10D3 /* CoinAnalyticsIssuesView.swift */; }; 11B35983B0C3D4EFC115043A /* MarkdownImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3563ED22080EE222848A5 /* MarkdownImageCell.swift */; }; - 11B35988DD7E3E4E2EEE4444 /* SendTonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ECB140E2565C55165A0 /* SendTonFactory.swift */; }; 11B35989090E4AD4E3EB8D72 /* CoinAuditsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ADF518A2F98FF673B4B /* CoinAuditsViewModel.swift */; }; 11B3598A03285D980219B256 /* CheckboxCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35216E1F4300730E08C5D /* CheckboxCell.swift */; }; 11B3598BE9C7A456A70B5DFD /* BackupVerifyWordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3576FCFC9394BA37975FC /* BackupVerifyWordsViewModel.swift */; }; @@ -1074,7 +1069,6 @@ 11B35C3A0B6DE83A66371224 /* SetPasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A10404D5E085E482CC7 /* SetPasscodeView.swift */; }; 11B35C3AFFA5B40481AF15B9 /* AccountRecord_v_0_19.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350F6C5F6ABC288511AF0 /* AccountRecord_v_0_19.swift */; }; 11B35C43886D9A0F0C69EF33 /* EvmAccountRestoreStateStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35999E6C5518115365410 /* EvmAccountRestoreStateStorage.swift */; }; - 11B35C4797940E34E6361A83 /* SendTonService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A296A67CE158347A785 /* SendTonService.swift */; }; 11B35C47A06C0A4F7231C511 /* NftCollectionAssetsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35100DD6E2DBF905FD19B /* NftCollectionAssetsModule.swift */; }; 11B35C4A0250F05179488A91 /* CexWithdrawNetworkRaw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EC03BB5316524050518 /* CexWithdrawNetworkRaw.swift */; }; 11B35C4D4120D85CD32CAD0F /* TransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350CCAA0C9F2F5279F680 /* TransactionsViewController.swift */; }; @@ -3626,7 +3620,6 @@ 11B35A1AE56A94BEB52AC4D1 /* StorageMigrator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageMigrator.swift; sourceTree = ""; }; 11B35A1C200EC15159154E3F /* ShortcutInputCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutInputCell.swift; sourceTree = ""; }; 11B35A296048CDD27A26FE9E /* EvmAccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EvmAccountManager.swift; sourceTree = ""; }; - 11B35A296A67CE158347A785 /* SendTonService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTonService.swift; sourceTree = ""; }; 11B35A2C34C3D62CCA5BFFB5 /* GuidesRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GuidesRepository.swift; sourceTree = ""; }; 11B35A309C359456D7DF1A03 /* AppIconManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIconManager.swift; sourceTree = ""; }; 11B35A36FFDA63E9668F1B24 /* CexWithdrawConfirmService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CexWithdrawConfirmService.swift; sourceTree = ""; }; @@ -3832,7 +3825,6 @@ 11B35EC03BB5316524050518 /* CexWithdrawNetworkRaw.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CexWithdrawNetworkRaw.swift; sourceTree = ""; }; 11B35EC5CADBD290DDD3DE1C /* GuideCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GuideCell.swift; sourceTree = ""; }; 11B35EC9E0E936067225C787 /* PoolSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PoolSource.swift; sourceTree = ""; }; - 11B35ECB140E2565C55165A0 /* SendTonFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTonFactory.swift; sourceTree = ""; }; 11B35ECC6866F29A33129F06 /* NftHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftHeaderView.swift; sourceTree = ""; }; 11B35ED0A8819AB7EA27D368 /* StatExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatExtensions.swift; sourceTree = ""; }; 11B35EDC73170179EA8F4CBE /* MultiSwapModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiSwapModule.swift; sourceTree = ""; }; @@ -3853,7 +3845,6 @@ 11B35F4A3C8D3D2C6579FD94 /* AlertPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = ""; }; 11B35F4B9522FCCD91582AAF /* WalletElementServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletElementServiceFactory.swift; sourceTree = ""; }; 11B35F57D462E2C9E9AEF67C /* LockManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockManager.swift; sourceTree = ""; }; - 11B35F5835A746165C520333 /* SendTonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTonViewController.swift; sourceTree = ""; }; 11B35F5A3CC8C229C0849756 /* PublicKeysViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKeysViewController.swift; sourceTree = ""; }; 11B35F5B696CF0677865FA2C /* DuressModeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuressModeViewModel.swift; sourceTree = ""; }; 11B35F60AFA103D0CD2369C3 /* BlockchainTokensView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockchainTokensView.swift; sourceTree = ""; }; @@ -5991,16 +5982,6 @@ path = ManageWallets; sourceTree = ""; }; - 11B35805423FC0A1C859B08E /* Ton */ = { - isa = PBXGroup; - children = ( - 11B35F5835A746165C520333 /* SendTonViewController.swift */, - 11B35A296A67CE158347A785 /* SendTonService.swift */, - 11B35ECB140E2565C55165A0 /* SendTonFactory.swift */, - ); - path = Ton; - sourceTree = ""; - }; 11B3580CFABA60F3840C093E /* DuressMode */ = { isa = PBXGroup; children = ( @@ -8113,7 +8094,6 @@ ABC9A48552CF0C90E22686A9 /* SendBaseService.swift */, ABC9AAF2ADD900F32D87C7BE /* SendViewModelOld.swift */, ABC9ABE97578DC667CBDC11A /* BaseSendViewController.swift */, - 11B35805423FC0A1C859B08E /* Ton */, ); path = Platforms; sourceTree = ""; @@ -10528,9 +10508,6 @@ 11B35927C89712EF8ED36981 /* InputRowModifier.swift in Sources */, 11B359926B194C0207B1C8E6 /* PreSendView.swift in Sources */, 11B35FA298822DABDB1CD109 /* PreSendViewModel.swift in Sources */, - 11B35504EF11FA59D2A358BE /* SendTonViewController.swift in Sources */, - 11B35C4797940E34E6361A83 /* SendTonService.swift in Sources */, - 11B35988DD7E3E4E2EEE4444 /* SendTonFactory.swift in Sources */, 11B351581C801BF3A8415DBE /* TonAddressParserItem.swift in Sources */, ABC9AA19453EBE2970EE6AE5 /* ReceiveAddressView.swift in Sources */, ABC9A57D1DA5481E44DEE8C0 /* PrimaryCircleButtonStyle.swift in Sources */, @@ -11988,9 +11965,6 @@ 11B359D912BC5502A9FA0E57 /* InputRowModifier.swift in Sources */, 11B35283F8170DB664A77C2B /* PreSendView.swift in Sources */, 11B3598EE09251933E7FFD5D /* PreSendViewModel.swift in Sources */, - 11B3582C5AB8B2BF15962AE7 /* SendTonViewController.swift in Sources */, - 11B358AFE8EC87CA1DEB4C22 /* SendTonService.swift in Sources */, - 11B3564E05702B4253453F23 /* SendTonFactory.swift in Sources */, 11B3556CAD6FE74AB654E8B2 /* TonAddressParserItem.swift in Sources */, ABC9A8787536855069557477 /* ReceiveAddressView.swift in Sources */, ABC9AD530352E51084B1B4B7 /* PrimaryCircleButtonStyle.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift index 9449b4cafd..5bf50cf54d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/JettonAdapter.swift @@ -120,44 +120,34 @@ extension JettonAdapter: IDepositAdapter { } 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 { + func estimateFee(recipient: FriendlyAddress, amount: TonAdapter.SendAmount, 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) + let kitFee = try await tonKit.estimateFee(jettonWallet: jettonBalance.walletAddress, recipient: recipient, amount: sendAmount(jettonBalance: jettonBalance, amount: amount), comment: comment) + return TonAdapter.amount(kitAmount: kitFee) } - func send(recipient: String, amount: Decimal, comment: String?) async throws { + func send(recipient: FriendlyAddress, amount: TonAdapter.SendAmount, 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) + try await tonKit.send(jettonWallet: jettonBalance.walletAddress, recipient: recipient, amount: sendAmount(jettonBalance: jettonBalance, amount: amount), comment: comment) + } + + private func sendAmount(jettonBalance: JettonBalance, amount: TonAdapter.SendAmount) throws -> BigUInt { + switch amount { + case let .amount(value): + guard let value = BigUInt(value.hs.roundedString(decimal: jettonBalance.jetton.decimals)) else { + throw AmountError.invalidAmount + } - guard let kitAmount = BigUInt(amount.description) else { + return value + case .max: throw AmountError.invalidAmount } - - try await tonKit.send(jettonWallet: jettonBalance.walletAddress, recipient: recipient, amount: kitAmount, comment: comment) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift index afb7642c42..8914771d23 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonAdapter.swift @@ -41,30 +41,6 @@ class TonAdapter { .sink { [weak self] in self?.balanceData = BalanceData(available: Self.amount(kitAmount: $0?.balance)) } .store(in: &cancellables) } - - 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(kitAmount: BigUInt?) -> Decimal { - guard let kitAmount, let significand = Decimal(string: kitAmount.description) else { - return 0 - } - - return Decimal(sign: .plus, exponent: -Self.decimals, significand: significand) - } - - static func amount(kitAmount: String) -> Decimal { - amount(kitAmount: BigUInt(kitAmount)) - } - - static func amount(kitAmount: Int64) -> Decimal { - amount(kitAmount: BigUInt(kitAmount)) - } } extension TonAdapter: IBaseAdapter { @@ -112,40 +88,61 @@ extension TonAdapter: IDepositAdapter { } extension TonAdapter: ISendTonAdapter { - var availableBalance: Decimal { - balanceData.available + func estimateFee(recipient: FriendlyAddress, amount: SendAmount, comment: String?) async throws -> Decimal { + let kitFee = try await tonKit.estimateFee(recipient: recipient, amount: sendAmount(amount: amount), comment: comment) + return Self.amount(kitAmount: kitFee) } - func validate(address: String) throws { - _ = try FriendlyAddress(string: address) + func send(recipient: FriendlyAddress, amount: SendAmount, comment: String?) async throws { + try await tonKit.send(recipient: recipient, amount: sendAmount(amount: amount), comment: comment) } - 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) + private func sendAmount(amount: SendAmount) throws -> Kit.SendAmount { + switch amount { + case let .amount(value): + guard let value = BigUInt(value.hs.roundedString(decimal: Self.decimals)) else { + throw AmountError.invalidAmount + } - guard let kitAmount = BigUInt(amount.description) else { - throw AmountError.invalidAmount + return .amount(value: value) + case .max: + return .max } - - let kitFee = try await tonKit.estimateFee(recipient: recipient, amount: .amount(value: kitAmount), comment: comment) - - return Self.amount(kitAmount: kitFee) } +} - 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) +extension TonAdapter { + 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) + } + } - guard let kitAmount = BigUInt(amount.description) else { - throw AmountError.invalidAmount + static func amount(kitAmount: BigUInt?) -> Decimal { + guard let kitAmount, let significand = Decimal(string: kitAmount.description) else { + return 0 } - try await tonKit.send(recipient: recipient, amount: .amount(value: kitAmount), comment: comment) + return Decimal(sign: .plus, exponent: -Self.decimals, significand: significand) + } + + static func amount(kitAmount: String) -> Decimal { + amount(kitAmount: BigUInt(kitAmount)) + } + + static func amount(kitAmount: Int64) -> Decimal { + amount(kitAmount: BigUInt(kitAmount)) } } extension TonAdapter { + enum SendAmount { + case amount(value: Decimal) + case max + } + enum AmountError: Error { case invalidAmount } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift index 20e72e9fa1..40631f93c1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Adapters/TonTransactionAdapter.swift @@ -101,8 +101,6 @@ extension TonTransactionAdapter: ITransactionsAdapter { if let jettonAddress = tagToken.jettonAddress { tokenType = .jetton(address: jettonAddress.toString(bounceable: true)) } - default: - () } guard let tokenType else { diff --git a/UnstoppableWallet/UnstoppableWallet/Core/App.swift b/UnstoppableWallet/UnstoppableWallet/Core/App.swift index e41a3f45fd..71af97cfc3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/App.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/App.swift @@ -337,4 +337,11 @@ class App { nftMetadataSyncer: nftMetadataSyncer ) } + + func newSendEnabled(wallet: Wallet) -> Bool { + switch wallet.token.blockchainType { + case .ton: return true + default: return localStorage.newSendEnabled + } + } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift b/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift index 8a6f666a83..b9024e3a00 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift @@ -8,6 +8,7 @@ import HsToolKit import MarketKit import RxSwift import ThemeKit +import TonSwift import TronKit import UIKit import UniswapKit @@ -101,10 +102,8 @@ protocol ISendTronAdapter { } protocol ISendTonAdapter { - var availableBalance: Decimal { get } - func validate(address: String) throws - func estimateFee(recipient: String, amount: Decimal, comment: String?) async throws -> Decimal - func send(recipient: String, amount: Decimal, comment: String?) async throws + func estimateFee(recipient: FriendlyAddress, amount: TonAdapter.SendAmount, comment: String?) async throws -> Decimal + func send(recipient: FriendlyAddress, amount: TonAdapter.SendAmount, comment: String?) async throws } protocol IErc20Adapter { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/SendModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/SendModule.swift index e5d0085354..ab4fd34a28 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/SendModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/SendModule.swift @@ -26,9 +26,6 @@ enum SendModule { return SendEvmModule.viewController(token: token, mode: mode, adapter: adapter) case let adapter as ISendTronAdapter: return SendTronModule.viewController(token: token, mode: mode, adapter: adapter) - case let adapter as ISendTonAdapter: -// return SendModuleNew.view(adapter: adapter).toViewController() - return Self.viewController(token: token, mode: mode, adapter: adapter) default: return nil } } @@ -356,94 +353,4 @@ enum SendModule { return viewController } - - private static func viewController(token: Token, mode: PreSendViewModel.Mode, adapter: ISendTonAdapter) -> UIViewController? { - let switchService = AmountTypeSwitchService(userDefaultsStorage: App.shared.userDefaultsStorage) - let coinService = CoinService(token: token, currencyManager: App.shared.currencyManager, marketKit: App.shared.marketKit) - let fiatService = FiatService(switchService: switchService, currencyManager: App.shared.currencyManager, marketKit: App.shared.marketKit) - - // Amount - let amountInputService = SendBitcoinAmountInputService(token: token) - let amountCautionService = SendAmountCautionService(amountInputService: amountInputService) - - // Address - let parserItem = TonAddressParserItem() - let addressParserChain = AddressParserChain() - .append(handler: parserItem) - - let addressUriParser = AddressParserFactory.parser(blockchainType: token.blockchainType, tokenType: token.type) - let addressService = AddressService(mode: .parsers(addressUriParser, addressParserChain), marketKit: App.shared.marketKit, contactBookManager: App.shared.contactManager, blockchainType: token.blockchainType) - - let memoService = SendMemoInputService(maxSymbols: 120) - - // Fee - let feeFiatService = FiatService(switchService: switchService, currencyManager: App.shared.currencyManager, marketKit: App.shared.marketKit) - let feeService = SendFeeService(fiatService: feeFiatService, feeToken: token) - - let service = SendTonService( - amountService: amountInputService, - amountCautionService: amountCautionService, - addressService: addressService, - memoService: memoService, - adapter: adapter, - reachabilityManager: App.shared.reachabilityManager, - token: token, - mode: mode - ) - - // Add dependencies - switchService.add(toggleAllowedObservable: fiatService.toggleAvailableObservable) - - amountInputService.availableBalanceService = service - amountCautionService.availableBalanceService = service - - memoService.availableService = service - feeService.feeValueService = service - - // ViewModels - let viewModel = SendViewModelOld(service: service) - let availableBalanceViewModel = SendAvailableBalanceViewModel(service: service, coinService: coinService, switchService: switchService) - let amountInputViewModel = AmountInputViewModel( - service: amountInputService, - fiatService: fiatService, - switchService: switchService, - decimalParser: AmountDecimalParser() - ) - addressService.amountPublishService = amountInputViewModel - - let amountCautionViewModel = SendAmountCautionViewModel( - service: amountCautionService, - switchService: switchService, - coinService: coinService - ) - let recipientViewModel = RecipientAddressViewModel(service: addressService, handlerDelegate: nil) - let memoViewModel = SendMemoInputViewModel(service: memoService) - - // Fee - let feeViewModel = SendFeeViewModel(service: feeService) - - // Confirmation and Settings - let sendFactory = SendTonFactory( - service: service, - fiatService: fiatService, - addressService: addressService, - memoService: memoService, - feeFiatService: feeFiatService, - logger: App.shared.logger, - token: token - ) - - let viewController = SendTonViewController( - confirmationFactory: sendFactory, - viewModel: viewModel, - availableBalanceViewModel: availableBalanceViewModel, - amountInputViewModel: amountInputViewModel, - amountCautionViewModel: amountCautionViewModel, - recipientViewModel: recipientViewModel, - memoViewModel: memoViewModel, - feeViewModel: feeViewModel - ) - - return viewController - } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonFactory.swift deleted file mode 100644 index 59e627149b..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonFactory.swift +++ /dev/null @@ -1,57 +0,0 @@ -import HsToolKit -import MarketKit -import UIKit - -class SendTonFactory: BaseSendFactory { - private let service: SendTonService - private let fiatService: FiatService - private let feeFiatService: FiatService - private let addressService: AddressService - private let memoService: SendMemoInputService - private let logger: Logger - private let token: Token - - init(service: SendTonService, fiatService: FiatService, addressService: AddressService, memoService: SendMemoInputService, feeFiatService: FiatService, logger: Logger, token: Token) { - self.service = service - self.fiatService = fiatService - self.feeFiatService = feeFiatService - self.addressService = addressService - self.memoService = memoService - self.logger = logger - self.token = token - } - - private func items() throws -> [ISendConfirmationViewItemNew] { - var viewItems = [ISendConfirmationViewItemNew]() - - guard let address = addressService.state.address else { - throw ConfirmationError.noAddress - } - - let (coinValue, currencyValue) = try values(fiatService: fiatService) - let (feeCoinValue, feeCurrencyValue) = try values(fiatService: feeFiatService) - - viewItems.append(SendConfirmationAmountViewItem(coinValue: coinValue, currencyValue: currencyValue, receiver: address)) - - if memoService.isAvailable, let memo = memoService.memo, !memo.isEmpty { - viewItems.append(SendConfirmationMemoViewItem(memo: memo)) - } - - viewItems.append(SendConfirmationFeeViewItem(coinValue: feeCoinValue, currencyValue: feeCurrencyValue)) - - return viewItems - } -} - -extension SendTonFactory: ISendConfirmationFactory { - func confirmationViewController() throws -> UIViewController { - let items = try items() - - let service = SendConfirmationService(sendService: service, logger: logger, token: token, items: items) - let contactLabelService = ContactLabelService(contactManager: App.shared.contactManager, blockchainType: .ton) - let viewModel = SendConfirmationViewModel(service: service, contactLabelService: contactLabelService) - let viewController = SendConfirmationViewController(viewModel: viewModel) - - return viewController - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift deleted file mode 100644 index f742f0596a..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonService.swift +++ /dev/null @@ -1,200 +0,0 @@ -import Foundation -import HsExtensions -import HsToolKit -import MarketKit -import RxRelay -import RxSwift - -class SendTonService { - private let disposeBag = DisposeBag() - private let scheduler = SerialDispatchQueueScheduler(qos: .userInitiated, internalSerialQueueName: "\(AppConfig.label).send-bitcoin-service") - - let token: Token - let mode: PreSendViewModel.Mode - - private let amountService: IAmountInputService - private let amountCautionService: SendAmountCautionService - private let addressService: AddressService - private let memoService: SendMemoInputService - private let adapter: ISendTonAdapter - private var tasks = Set() - - private let stateRelay = PublishRelay() - private(set) var state: SendBaseService.State = .notReady { - didSet { - stateRelay.accept(state) - } - } - - private let feeStateRelay = BehaviorRelay>(value: .loading) - private(set) var feeState: DataStatus = .loading { - didSet { - if !feeState.equalTo(oldValue) { - feeStateRelay.accept(feeState) - syncState() - } - } - } - - private let availableBalanceRelay: BehaviorRelay> - private(set) var availableBalance: DataStatus { - didSet { - if !availableBalance.equalTo(oldValue) { - availableBalanceRelay.accept(availableBalance) - } - } - } - - init(amountService: IAmountInputService, amountCautionService: SendAmountCautionService, addressService: AddressService, memoService: SendMemoInputService, adapter: ISendTonAdapter, reachabilityManager: IReachabilityManager, token: Token, mode: PreSendViewModel.Mode) { - self.amountService = amountService - self.amountCautionService = amountCautionService - self.addressService = addressService - self.memoService = memoService - self.adapter = adapter - self.token = token - self.mode = mode - - switch mode { - case let .prefilled(address, amount): - addressService.set(text: address) - if let amount { addressService.publishAmountRelay.accept(amount) } - case let .predefined(address): addressService.set(text: address) - case .regular: () - } - - availableBalance = .completed(adapter.availableBalance) - availableBalanceRelay = .init(value: .completed(adapter.availableBalance)) - - subscribe(MainScheduler.instance, disposeBag, reachabilityManager.reachabilityObservable) { [weak self] isReachable in - if isReachable { - self?.updateFee() - } - } - - subscribe(scheduler, disposeBag, amountService.amountObservable) { [weak self] _ in self?.updateFee() } - subscribe(scheduler, disposeBag, amountCautionService.amountCautionObservable) { [weak self] _ in self?.updateFee() } - subscribe(scheduler, disposeBag, addressService.stateObservable) { [weak self] _ in self?.updateFee() } - - loadFee() - } - - private func updateFee() { - loadFee() - } - - private func syncState() { - guard amountCautionService.amountCaution == nil, !amountService.amount.isZero else { - state = .notReady - return - } - - if addressService.state.isLoading { - state = .loading - return - } - - guard addressService.state.address != nil else { - state = .notReady - return - } - - guard feeState.data != nil else { - state = .notReady - return - } - - state = .ready - } - - private func params() throws -> (Address, Decimal, String?) { - let address: Address - switch addressService.state { - case let .success(sendAddress): address = sendAddress - case let .fetchError(error): throw error - default: throw AppError.addressInvalid - } - - let amount = amountService.amount - - guard !amount.isZero else { - throw SendTransactionError.wrongAmount - } - - let memo = memoService.memo - return (address, amount, memo) - } - - private func loadFee() { - do { - let data = try params() - feeState = .loading - - Task { [weak self, adapter] in - do { - 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 { - self?.feeState = .failed(error) - self?.availableBalance = .completed(adapter.availableBalance) - } - } - .store(in: &tasks) - } catch { - feeState = .failed(error) - availableBalance = .completed(adapter.availableBalance) - } - } -} - -extension SendTonService: ISendBaseService { - var stateObservable: Observable { - stateRelay.asObservable() - } -} - -extension SendTonService: ISendService { - func sendSingle(logger _: HsToolKit.Logger) -> Single { - do { - let data = try params() - return Single.create { [adapter] observer in - let task = Task { [adapter] in - do { - try await adapter.send(recipient: data.0.raw, amount: data.1, comment: data.2) - observer(.success(())) - } catch { - observer(.error(error)) - } - } - - return Disposables.create { - task.cancel() - } - } - } catch { - return Single.error(error) - } - } -} - -extension SendTonService: ISendXFeeValueService { - var feeStateObservable: Observable> { - feeStateRelay.asObservable() - } -} - -extension SendTonService: IAvailableBalanceService { - var availableBalanceObservable: Observable> { - availableBalanceRelay.asObservable() - } -} - -extension SendTonService: IMemoAvailableService { - var isAvailable: Bool { - true - } - - var isAvailableObservable: Observable { - Observable.empty() - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonViewController.swift deleted file mode 100644 index cc5ee21204..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Send/Platforms/Ton/SendTonViewController.swift +++ /dev/null @@ -1,75 +0,0 @@ -import SectionsTableView -import SnapKit -import ThemeKit -import UIKit - -class SendTonViewController: BaseSendViewController { - private let feeCell: FeeCell - - init(confirmationFactory: ISendConfirmationFactory, - viewModel: SendViewModelOld, - availableBalanceViewModel: SendAvailableBalanceViewModel, - amountInputViewModel: AmountInputViewModel, - amountCautionViewModel: SendAmountCautionViewModel, - recipientViewModel: RecipientAddressViewModel, - memoViewModel: SendMemoInputViewModel, - feeViewModel: SendFeeViewModel) - { - feeCell = FeeCell(viewModel: feeViewModel, title: "fee_settings.fee".localized) - - super.init( - confirmationFactory: confirmationFactory, - viewModel: viewModel, - availableBalanceViewModel: availableBalanceViewModel, - amountInputViewModel: amountInputViewModel, - amountCautionViewModel: amountCautionViewModel, - recipientViewModel: recipientViewModel, - memoViewModel: memoViewModel - ) - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - feeCell.onOpenInfo = { [weak self] in - self?.openInfo(title: "fee_settings.fee".localized, description: "fee_settings.fee.info".localized) - } - - didLoad() - } - - private func openInfo(title: String, description: String) { - let viewController = BottomSheetModule.description(title: title, text: description) - present(viewController, animated: true) - } - - var feeSection: SectionProtocol { - Section( - id: "fee", - headerState: .margin(height: .margin12), - rows: [ - StaticRow( - cell: feeCell, - id: "fee", - height: .heightCell48 - ), - ] - ) - } - - override func buildSections() -> [SectionProtocol] { - var sections = super.buildSections() - sections.append(contentsOf: [ - memoSection, - feeSection, - buttonSection, - ]) - - return sections - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendData.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendData.swift index 73f44cc084..114c088a10 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendData.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendData.swift @@ -2,6 +2,7 @@ import BitcoinCore import EvmKit import Foundation import MarketKit +import TonSwift import TronKit import ZcashLightClientKit @@ -10,8 +11,8 @@ enum SendData { case bitcoin(token: Token, params: SendParameters) case binance(token: Token, amount: Decimal, address: String, memo: String?) case zcash(amount: Decimal, recipient: Recipient, memo: String?) - case tron(token: Token, contract: Contract) - case ton(amount: Decimal, address: String, memo: String?) + case tron(token: Token, contract: TronKit.Contract) + case ton(token: Token, amount: Decimal, address: FriendlyAddress, memo: String?) case swap(tokenIn: Token, tokenOut: Token, amountIn: Decimal, provider: IMultiSwapProvider) case walletConnect(request: WalletConnectRequest) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendHandlerFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendHandlerFactory.swift index a65650e9d7..8fb966e8bf 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendHandlerFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/SendHandlerFactory.swift @@ -13,8 +13,8 @@ enum SendHandlerFactory { return ZcashSendHandler.instance(amount: amount, recipient: recipient, memo: memo) case let .tron(token, contract): return TronSendHandler.instance(token: token, contract: contract) - case let .ton(amount, address, memo): - return TonSendHandler.instance(amount: amount, address: address, memo: memo) + case let .ton(token, amount, address, memo): + return TonSendHandler.instance(token: token, amount: amount, address: address, memo: memo) case let .swap(tokenIn, tokenOut, amountIn, provider): return MultiSwapSendHandler.instance(tokenIn: tokenIn, tokenOut: tokenOut, amountIn: amountIn, provider: provider) case let .walletConnect(request): @@ -46,7 +46,7 @@ enum SendHandlerFactory { } if let adapter = adapter as? ISendTonAdapter & IBalanceAdapter { - return TonPreSendHandler(adapter: adapter) + return TonPreSendHandler(token: wallet.token, adapter: adapter) } return nil diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonPresendHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonPresendHandler.swift index 6b3b4c15c4..a613ce792e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonPresendHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonPresendHandler.swift @@ -1,19 +1,21 @@ -import BigInt import Combine import Foundation import MarketKit import RxSwift import TonKit +import TonSwift class TonPreSendHandler { - private let adapter: ISendTonAdapter & IBalanceAdapter + private let token: Token + private let adapter: IBalanceAdapter private let stateSubject = PassthroughSubject() private let balanceSubject = PassthroughSubject() private let disposeBag = DisposeBag() - init(adapter: ISendTonAdapter & IBalanceAdapter) { + init(token: Token, adapter: IBalanceAdapter) { + self.token = token self.adapter = adapter adapter.balanceStateUpdatedObservable @@ -55,11 +57,10 @@ extension TonPreSendHandler: IPreSendHandler { func sendData(amount: Decimal, address: String, memo: String?) -> SendDataResult { do { - try TonKit.Kit.validate(address: address) + let address = try FriendlyAddress(string: address) + return .valid(sendData: .ton(token: token, amount: amount, address: address, memo: memo)) } catch { return .invalid(cautions: [CautionNew(text: error.smartDescription, type: .error)]) } - - return .valid(sendData: .ton(amount: amount, address: address, memo: memo)) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift index caa17bfad1..d80c12a0b4 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/TonSendHandler.swift @@ -2,16 +2,21 @@ import BigInt import Foundation import MarketKit import TonKit +import TonSwift class TonSendHandler { + private let tonKit: TonKit.Kit private let token: Token - private let adapter: ISendTonAdapter - private var amount: Decimal - private let address: String + let baseToken: Token + private let adapter: ISendTonAdapter & IBalanceAdapter + private let amount: Decimal + private let address: FriendlyAddress private let memo: String? - init(token: Token, adapter: ISendTonAdapter, amount: Decimal, address: String, memo: String?) { + init(tonKit: TonKit.Kit, token: Token, baseToken: Token, adapter: ISendTonAdapter & IBalanceAdapter, amount: Decimal, address: FriendlyAddress, memo: String?) { + self.tonKit = tonKit self.token = token + self.baseToken = baseToken self.adapter = adapter self.amount = amount self.address = address @@ -19,27 +24,100 @@ class TonSendHandler { } } +extension TonSendHandler: ISendHandler { + var expirationDuration: Int? { + 10 + } + + func sendData(transactionSettings _: TransactionSettings?) async throws -> ISendData { + var fee: Decimal? + var transactionError: Error? + let tonBalance = TonAdapter.amount(kitAmount: tonKit.account?.balance) + + var sendAmount: TonAdapter.SendAmount = .amount(value: amount) + + if token.type.isNative, amount == tonBalance { + sendAmount = .max + } + + var finalAmount = amount + + do { + let estimatedFee = try await adapter.estimateFee(recipient: address, amount: sendAmount, comment: memo) + fee = estimatedFee + + if token.type.isNative { + switch sendAmount { + case .max: + finalAmount = max(0, finalAmount - estimatedFee) + + if finalAmount == 0 { + throw TransactionError.insufficientTonBalance(balance: tonBalance) + } + default: + if finalAmount + estimatedFee > tonBalance { + throw TransactionError.insufficientTonBalance(balance: tonBalance) + } + } + } else { + if tonBalance < estimatedFee { + throw TransactionError.insufficientTonBalance(balance: estimatedFee) + } + } + } catch { + transactionError = error + } + + return SendData( + token: token, + amount: finalAmount, + address: address, + memo: memo, + fee: fee, + transactionError: transactionError, + sendAmount: sendAmount + ) + } + + func send(data: ISendData) async throws { + guard let data = data as? SendData else { + throw SendError.invalidData + } + + _ = try await adapter.send( + recipient: data.address, + amount: data.sendAmount, + comment: data.memo + ) + } +} + extension TonSendHandler { class SendData: ISendData { private let token: Token private let amount: Decimal - private let address: String - private let memo: String? - let fee: Decimal? + let address: FriendlyAddress + let memo: String? + private let fee: Decimal? private let transactionError: Error? - var feeData: FeeData? = nil + let sendAmount: TonAdapter.SendAmount - init(token: Token, amount: Decimal, address: String, memo: String?, fee: Decimal?, transactionError: Error?) { + init(token: Token, amount: Decimal, address: FriendlyAddress, memo: String?, fee: Decimal?, transactionError: Error?, sendAmount: TonAdapter.SendAmount) { self.token = token self.amount = amount self.address = address self.memo = memo self.fee = fee self.transactionError = transactionError + self.sendAmount = sendAmount + } + + var feeData: FeeData? { + nil } var canSend: Bool { - fee != nil && transactionError == nil + transactionError == nil } var customSendButtonTitle: String? { @@ -50,25 +128,21 @@ extension TonSendHandler { [token.coin] } - func caution(transactionError: Error, feeToken: Token) -> CautionNew { + private func caution(transactionError: Error, feeToken: Token) -> CautionNew { let title: String let text: String if let tonError = transactionError as? TonSendHandler.TransactionError { switch tonError { - case let .insufficientBalance(balance): + case let .insufficientTonBalance(balance): let coinValue = CoinValue(kind: .token(token: feeToken), value: balance) let balanceString = ValueFormatter.instance.formatShort(coinValue: coinValue) title = "fee_settings.errors.insufficient_balance".localized text = "fee_settings.errors.insufficient_balance.info".localized(balanceString ?? "") - - case .zeroAmount: - title = "alert.error".localized - text = "fee_settings.errors.zero_amount.info".localized } } else { - title = "Error" + title = "ethereum_transaction.error.title".localized text = transactionError.convertedError.smartDescription } @@ -85,7 +159,7 @@ extension TonSendHandler { return cautions } - func sections(baseToken _: Token, currency: Currency, rates: [String: Decimal]) -> [[SendField]] { + func sections(baseToken: Token, currency: Currency, rates: [String: Decimal]) -> [[SendField]] { var fields: [SendField] = [ .amount( title: "send.confirmation.you_send".localized, @@ -96,7 +170,7 @@ extension TonSendHandler { ), .address( title: "send.confirmation.to".localized, - value: address, + value: address.toString(), blockchainType: .ton ), ] @@ -107,15 +181,15 @@ extension TonSendHandler { return [ fields, - feeFields(currency: currency, feeTokenRate: rates[token.coin.uid]), + feeFields(currency: currency, feeToken: baseToken, feeTokenRate: rates[token.coin.uid]), ] } - private func feeFields(currency: Currency, feeTokenRate: Decimal?) -> [SendField] { + private func feeFields(currency: Currency, feeToken: Token, feeTokenRate: Decimal?) -> [SendField] { var viewItems = [SendField]() if let fee { - let coinValue = CoinValue(kind: .token(token: token), value: fee) + let coinValue = CoinValue(kind: .token(token: feeToken), value: fee) let currencyValue = feeTokenRate.map { CurrencyValue(currency: currency, value: fee * $0) } viewItems.append( @@ -134,98 +208,35 @@ extension TonSendHandler { } } -extension TonSendHandler: ISendHandler { - var baseToken: Token { - token - } - - var expirationDuration: Int? { - 10 - } - - func sendData(transactionSettings _: TransactionSettings?) async throws -> ISendData { - var fee: Decimal? - var transactionError: Error? - let tonBalance = adapter.availableBalance - - do { - let _fee = try await adapter.estimateFee(recipient: address, amount: amount, comment: memo) - var totalAmount = Decimal.zero - - var sentAmount = amount - if tonBalance == amount { - // If the maximum amount is being sent, then we subtract fees from sent amount - sentAmount = sentAmount - _fee - - guard sentAmount > 0 else { - throw TransactionError.zeroAmount - } - - amount = sentAmount - } - - totalAmount += sentAmount - totalAmount += _fee - fee = _fee - - if tonBalance < totalAmount { - throw TransactionError.insufficientBalance(balance: tonBalance) - } - } catch { - transactionError = error - } - - return SendData( - token: token, - amount: amount, - address: address, - memo: memo, - fee: fee, - transactionError: transactionError - ) - } - - func send(data: ISendData) async throws { - guard let data = data as? SendData else { - throw SendError.invalidData - } - - guard data.fee != nil else { - throw SendError.noFees - } - - _ = try await adapter.send( - recipient: address, - amount: amount, - comment: memo - ) - } -} - extension TonSendHandler { enum SendError: Error { + case invalidAmount case invalidData - case noFees } enum TransactionError: Error { - case insufficientBalance(balance: Decimal) - case zeroAmount + case insufficientTonBalance(balance: Decimal) } } extension TonSendHandler { - static func instance(amount: Decimal, address: String, memo: String?) -> TonSendHandler? { + static func instance(token: Token, amount: Decimal, address: FriendlyAddress, memo: String?) -> TonSendHandler? { guard let baseToken = try? App.shared.coinManager.token(query: .init(blockchainType: .ton, tokenType: .native)) else { return nil } - guard let adapter = App.shared.adapterManager.adapter(for: baseToken) as? ISendTonAdapter else { + guard let adapter = App.shared.adapterManager.adapter(for: token) as? ISendTonAdapter & IBalanceAdapter else { + return nil + } + + guard let tonKit = App.shared.tonKitManager.tonKit else { return nil } return TonSendHandler( - token: baseToken, + tonKit: tonKit, + token: token, + baseToken: baseToken, adapter: adapter, amount: amount, address: address, diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceDataSource.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceDataSource.swift index 1755b8b5cd..dc42062a00 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceDataSource.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/Token/DataSources/WalletTokenBalance/WalletTokenBalanceDataSource.swift @@ -172,7 +172,7 @@ class WalletTokenBalanceDataSource: NSObject { } case let .wallet(wallet): cell.actions[.send] = { [weak self] in - let module = App.shared.localStorage.newSendEnabled ? PreSendView(wallet: wallet, showIcon: true).toNavigationViewController() : SendModule.controller(wallet: wallet).map { ThemeNavigationController(rootViewController: $0) } + let module = App.shared.newSendEnabled(wallet: wallet) ? PreSendView(wallet: wallet, showIcon: true).toNavigationViewController() : SendModule.controller(wallet: wallet).map { ThemeNavigationController(rootViewController: $0) } if let module { self?.parentViewController?.present(module, animated: true) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletModule.swift index a3b29a668c..5a4ad394a2 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletModule.swift @@ -102,7 +102,7 @@ enum WalletModule { let viewController = WalletTokenListViewController(viewModel: viewModel, dataSource: dataSourceChain) dataSource.viewController = viewController dataSource.onSelectWallet = { [weak viewController] wallet in - let module = App.shared.localStorage.newSendEnabled ? + let module = App.shared.newSendEnabled(wallet: wallet) ? PreSendView(wallet: wallet, mode: mode, onDismiss: { viewController?.dismiss(animated: true) }).toViewController() : SendModule.controller(wallet: wallet, mode: mode)