diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index a53b1c3ab..287447b79 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -758,6 +758,8 @@ 873F8063246E8E3E00EEE5EF /* SelectCurrencyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */; }; 874015BF270DBB4800B3515F /* MimeType+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874015BE270DBB4800B3515F /* MimeType+Extension.swift */; }; 8743CB50255059780039E469 /* DomainResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8743CB4F255059780039E469 /* DomainResolver.swift */; }; + 874527D7270B3A25008DB272 /* ArbitrumBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874527D6270B3A25008DB272 /* ArbitrumBridge.swift */; }; + 874527D9270B3A45008DB272 /* xDaiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874527D8270B3A45008DB272 /* xDaiBridge.swift */; }; 874AF0832603405F00D613A5 /* LoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874AF0822603405F00D613A5 /* LoadingIndicatorView.swift */; }; 874BD2BB2669F65800E62E02 /* PopularTokensCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874BD2BA2669F65800E62E02 /* PopularTokensCollection.swift */; }; 874C6D8125E3FF2300AD8380 /* ConfirmationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874C6D8025E3FF2300AD8380 /* ConfirmationHeaderView.swift */; }; @@ -1762,6 +1764,8 @@ 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectCurrencyButton.swift; sourceTree = ""; }; 874015BE270DBB4800B3515F /* MimeType+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MimeType+Extension.swift"; sourceTree = ""; }; 8743CB4F255059780039E469 /* DomainResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainResolver.swift; sourceTree = ""; }; + 874527D6270B3A25008DB272 /* ArbitrumBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArbitrumBridge.swift; sourceTree = ""; }; + 874527D8270B3A45008DB272 /* xDaiBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDaiBridge.swift; sourceTree = ""; }; 874AF0822603405F00D613A5 /* LoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorView.swift; sourceTree = ""; }; 874BD2BA2669F65800E62E02 /* PopularTokensCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularTokensCollection.swift; sourceTree = ""; }; 874C6D8025E3FF2300AD8380 /* ConfirmationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationHeaderView.swift; sourceTree = ""; }; @@ -4508,6 +4512,8 @@ 8731E6DD2565269400A6A7DA /* Oneinch */, 8731E6DA2565269400A6A7DA /* Uniswap */, 87B65269256FBF36000EF927 /* SwapTokenService.swift */, + 874527D6270B3A25008DB272 /* ArbitrumBridge.swift */, + 874527D8270B3A45008DB272 /* xDaiBridge.swift */, ); path = SwapToken; sourceTree = ""; @@ -5280,6 +5286,7 @@ 2963B6B91F9A7EEA003063C1 /* CoinTicker.swift in Sources */, 29F1C85820036926003780D8 /* AppTracker.swift in Sources */, 293E62711FA2F63500CB0A66 /* InitialWalletCreationCoordinator.swift in Sources */, + 874527D9270B3A45008DB272 /* xDaiBridge.swift in Sources */, 291D73C61F7F500D00A8AB56 /* TransactionItemState.swift in Sources */, 29BE3FD21F707DC300F6BFC2 /* TransactionDataCoordinator.swift in Sources */, 29F1C85120032688003780D8 /* Address.swift in Sources */, @@ -5347,6 +5354,7 @@ 77872D302026DC570032D687 /* SplashViewController.swift in Sources */, 87BC89B826B82288005482F4 /* UIBarButtonItem.swift in Sources */, 29C80D4D1FB5202C0037B1E0 /* BalanceBaseViewModel.swift in Sources */, + 874527D7270B3A25008DB272 /* ArbitrumBridge.swift in Sources */, 87BB63E2265E759700FF702A /* PrivateBalanceFetcher.swift in Sources */, 29E14FD11F7F457D00185568 /* TransactionsStorage.swift in Sources */, 87ED8F97248540F90005C69B /* AdvancedSettingsViewController.swift in Sources */, diff --git a/AlphaWallet/Assets.xcassets/arbitrum.imageset/Contents.json b/AlphaWallet/Assets.xcassets/arbitrum.imageset/Contents.json new file mode 100644 index 000000000..dff305e61 --- /dev/null +++ b/AlphaWallet/Assets.xcassets/arbitrum.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icons-tokens-arbitrum.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Assets.xcassets/arbitrum.imageset/icons-tokens-arbitrum.pdf b/AlphaWallet/Assets.xcassets/arbitrum.imageset/icons-tokens-arbitrum.pdf new file mode 100644 index 000000000..b9fa39499 Binary files /dev/null and b/AlphaWallet/Assets.xcassets/arbitrum.imageset/icons-tokens-arbitrum.pdf differ diff --git a/AlphaWallet/Core/BuyToken/Ramp/Ramp.swift b/AlphaWallet/Core/BuyToken/Ramp/Ramp.swift index 681094c01..62615b1cb 100644 --- a/AlphaWallet/Core/BuyToken/Ramp/Ramp.swift +++ b/AlphaWallet/Core/BuyToken/Ramp/Ramp.swift @@ -21,13 +21,11 @@ struct Ramp: TokenActionsProvider, BuyTokenURLProviderType { case .xDai: return URL(string: "\(Constants.buyXDaiWitRampUrl)&userAddress=\(account.address.eip55String)") //TODO need to check if Ramp supports these? Or is it taken care of elsehwere - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: - if let asset = asset(for: token) { - let base = Constants.buyWitRampUrl(asset: asset.symbol) - return URL(string: "\(base)&userAddress=\(account.address.eip55String)") + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: + return asset(for: token).flatMap { + return URL(string: "\(Constants.buyWitRampUrl(asset: $0.symbol))&userAddress=\(account.address.eip55String)") } } - return nil } func actions(token: TokenActionsServiceKey) -> [TokenInstanceAction] { @@ -40,7 +38,7 @@ struct Ramp: TokenActionsProvider, BuyTokenURLProviderType { switch token.server { case .xDai: return true - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return asset(for: token) != nil } } diff --git a/AlphaWallet/Core/CoinTicker/CoinTickersFetcher.swift b/AlphaWallet/Core/CoinTicker/CoinTickersFetcher.swift index a12c4c8ab..022950bea 100644 --- a/AlphaWallet/Core/CoinTicker/CoinTickersFetcher.swift +++ b/AlphaWallet/Core/CoinTicker/CoinTickersFetcher.swift @@ -32,9 +32,9 @@ struct TokenMappedToTicker: Hashable { protocol CoinTickersFetcherType { var tickersSubscribable: Subscribable<[AddressAndRPCServer: CoinTicker]> { get } - var tickers: [AddressAndRPCServer: CoinTicker] { get } - func fetchPrices(forTokens tokens: ServerDictionary<[TokenMappedToTicker]>) -> Promise + func ticker(for addressAndPRCServer: AddressAndRPCServer) -> CoinTicker? + func fetchPrices(forTokens tokens: [TokenMappedToTicker]) -> Promise func fetchChartHistories(addressToRPCServerKey: AddressAndRPCServer, force: Bool, periods: [ChartHistoryPeriod]) -> Promise<[ChartHistory]> } @@ -59,9 +59,7 @@ class CoinTickersFetcher: CoinTickersFetcherType { var tickersSubscribable: Subscribable<[AddressAndRPCServer: CoinTicker]> { return cache.tickersSubscribable } - var tickers: [AddressAndRPCServer: CoinTicker] { - return cache.tickers - } + private static let queue: DispatchQueue = DispatchQueue(label: "com.CoinTickersFetcher.updateQueue") private let provider: MoyaProvider @@ -74,6 +72,16 @@ class CoinTickersFetcher: CoinTickersFetcherType { self.cache = cache } + func ticker(for addressAndPRCServer: AddressAndRPCServer) -> CoinTicker? { + //NOTE: If it doesn't include the price for the native token, hardwire it to use Ethereum's mainnet's native token price. + if addressAndPRCServer.server == .arbitrum && addressAndPRCServer.address.sameContract(as: Constants.nativeCryptoAddressInDatabase) { + let overridenAddressAndPRCServer: AddressAndRPCServer = .init(address: Constants.nativeCryptoAddressInDatabase, server: .main) + return cache.tickers[overridenAddressAndPRCServer] + } else { + return cache.tickers[addressAndPRCServer] + } + } + //Important in implementation to not cache the returned promise (which is used to further fetch prices). We only want to cache the promise/request for fetching supported tickers private static func fetchSupportedTickers(config: Config, provider: MoyaProvider, shouldRetry: Bool = true) -> Promise<[Ticker]> { if let promise = fetchSupportedTokensPromise { return promise } @@ -99,7 +107,7 @@ class CoinTickersFetcher: CoinTickersFetcherType { Self.fetchSupportedTickers(config: config, provider: provider) } - func fetchPrices(forTokens tokens: ServerDictionary<[TokenMappedToTicker]>) -> Promise { + func fetchPrices(forTokens tokens: [TokenMappedToTicker]) -> Promise { let cache = self.cache return firstly { fetchTickers(forTokens: tokens) @@ -151,8 +159,7 @@ class CoinTickersFetcher: CoinTickersFetcherType { } } - private func fetchTickers(forTokens tokens: ServerDictionary<[TokenMappedToTicker]>) -> Promise<(tickers: [AddressAndRPCServer: CoinTicker], tickerIds: [String])> { - let tokens = tokens.values.flatMap { $0 } + private func fetchTickers(forTokens tokens: [TokenMappedToTicker]) -> Promise<(tickers: [AddressAndRPCServer: CoinTicker], tickerIds: [String])> { guard !isFetchingPrices else { return .init(error: Error.alreadyFetchingPrices) } isFetchingPrices = true @@ -307,7 +314,8 @@ fileprivate struct Ticker: Codable { case .avalanche: return platform == "avalanche" case .polygon: return platform == "polygon-pos" case .fantom: return platform == "fantom" - case .poa, .kovan, .sokol, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain_testnet, .ropsten, .rinkeby, .heco, .heco_testnet, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .custom, .optimistic, .optimisticKovan, .cronosTestnet: + case .arbitrum: return platform == "arbitrum-one" + case .poa, .kovan, .sokol, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain_testnet, .ropsten, .rinkeby, .heco, .heco_testnet, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .custom, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return false } } @@ -320,6 +328,7 @@ fileprivate struct Ticker: Codable { case .binance_smart_chain: return true case .avalanche: return true case .polygon: return true + case .arbitrum: return true case .poa, .kovan, .sokol, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain_testnet, .ropsten, .rinkeby, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .custom, .optimistic, .optimisticKovan, .cronosTestnet: return false } diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift b/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift index b42555391..1803baa30 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift @@ -102,14 +102,19 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { seal.fulfill(tokenObjects) } }.then(on: queue, { tokenObjects in - return self.refreshBalance(tokenObjects: tokenObjects, updatePolicy: .all, force: false) + return self.refreshBalance(tokenObjects: tokenObjects, updatePolicy: .all, force: force) + }).recover(on: queue, { e -> Promise in + error(value: e) + throw e }) } private func refreshBalanceForNonErc721Or1155Tokens(tokens: [Activity.AssignedToken]) -> Promise<[PrivateBalanceFetcher.TokenBatchOperation]> { assert(!tokens.contains { $0.isERC721Or1155AndNotForTickets }) + guard !tokens.isEmpty else { return .value([]) } let promises = tokens.map { getBalanceForNonErc721Or1155Tokens(forToken: $0) } + let id = UUID().uuidString return when(resolved: promises).map { values -> [PrivateBalanceFetcher.TokenBatchOperation] in return values.compactMap { $0.optionalValue }.compactMap { $0 } @@ -143,9 +148,9 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { //NOTE: taking for first element means that values was updated return values.compactMap { $0.optionalValue }.compactMap { $0 }.first }) - }.get(on: queue, { balanceValueHasChange in + }.ensure({ self.isRefeshingBalance = false - + }).get(on: queue, { balanceValueHasChange in if let value = balanceValueHasChange, value { self.delegate?.didUpdate(in: self) } @@ -171,6 +176,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { let promise1 = refreshBalanceForNonErc721Or1155Tokens(tokens: notErc721Or1155Tokens) let promise2 = refreshBalanceForErc721Or1155Tokens(tokens: erc721Or1155Tokens) let tokensDatastore = self.tokensDatastore + return when(resolved: [promise1, promise2]).then(on: queue, { value -> Promise in let resolved: [TokenBatchOperation] = value.compactMap { $0.optionalValue }.flatMap { $0 } return tokensDatastore.batchUpdateTokenPromise(resolved).recover { _ -> Guarantee in @@ -182,6 +188,15 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { enum TokenBatchOperation { case add(ERCToken, shouldUpdateBalance: Bool) case update(tokenObject: Activity.AssignedToken, action: TokensDataStore.TokenUpdateAction) + + var updateAction: TokensDataStore.TokenUpdateAction? { + switch self { + case .add: + return nil + case .update(_, let action): + return action + } + } } private func getBalanceForNonErc721Or1155Tokens(forToken tokenObject: Activity.AssignedToken) -> Promise { @@ -213,6 +228,8 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { private func refreshBalanceForErc721Or1155Tokens(tokens: [Activity.AssignedToken]) -> Promise<[PrivateBalanceFetcher.TokenBatchOperation]> { assert(!tokens.contains { !$0.isERC721Or1155AndNotForTickets }) + guard !tokens.isEmpty else { return .value([]) } + return firstly { getTokensFromOpenSea() }.then(on: queue, { [weak self] contractToOpenSeaNonFungibles -> Guarantee<[PrivateBalanceFetcher.TokenBatchOperation]> in diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift index f6d1d0af9..cd7f2567a 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift @@ -117,13 +117,20 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { balanceFetchers[wallet]!.tokensDatastore(server: server) } + //NOTE: for case if we disable rpc server, we don't fetch ticker for its native crypto + private static var nativeCryptoForAllChains: [Activity.AssignedToken] { + return RPCServer.allCases.map { server in + Activity.AssignedToken.init(tokenObject: TokensDataStore.etherToken(forServer: server)) + } + } + private var availableTokenObjects: Promise> { Promise<[Activity.AssignedToken]> { seal in DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } let tokenObjects = strongSelf.balanceFetchers.map { $0.value.tokenObjects }.flatMap { $0 } - - seal.fulfill(tokenObjects) + + seal.fulfill(tokenObjects + Self.nativeCryptoForAllChains) } }.map(on: queue, { objects -> ServerDictionary<[TokenMappedToTicker]> in let uniqueTokenObjectsOfAllWallets = Set(objects) @@ -170,10 +177,12 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { firstly { availableTokenObjects }.then(on: queue, { values -> Promise in - self.coinTickersFetcher.fetchPrices(forTokens: values) + self.coinTickersFetcher.fetchPrices(forTokens: values.values.flatMap({ $0 })) }).done(on: queue, { _ in //no-op - }).cauterize() + }).catch({ e in + error(value: e) + }) } private func notifyWalletSummary() { diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift index 6b1159a83..ab5fa3bea 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift @@ -156,7 +156,7 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { } private func balanceViewModel(key tokenObject: TokenObject) -> BalanceBaseViewModel? { - let ticker = coinTickersFetcher.tickers[tokenObject.addressAndRPCServer] + let ticker = coinTickersFetcher.ticker(for: tokenObject.addressAndRPCServer) switch tokenObject.type { case .nativeCryptocurrency: @@ -222,7 +222,7 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { var balances = Set() for var tokenObject in tokenObjects { - tokenObject.ticker = coinTickersFetcher.tickers[tokenObject.addressAndRPCServer] + tokenObject.ticker = coinTickersFetcher.ticker(for: tokenObject.addressAndRPCServer) balances.insert(tokenObject) } diff --git a/AlphaWallet/Core/DomainResolver.swift b/AlphaWallet/Core/DomainResolver.swift index 91891afac..71080c927 100644 --- a/AlphaWallet/Core/DomainResolver.swift +++ b/AlphaWallet/Core/DomainResolver.swift @@ -113,7 +113,7 @@ fileprivate extension RPCServer { case .kovan: return "kovan" case .ropsten: return "ropsten" case .rinkeby: return "rinkeby" - case .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .custom, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .custom, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return nil } } diff --git a/AlphaWallet/Core/SwapToken/ArbitrumBridge.swift b/AlphaWallet/Core/SwapToken/ArbitrumBridge.swift new file mode 100644 index 000000000..602f9e9ca --- /dev/null +++ b/AlphaWallet/Core/SwapToken/ArbitrumBridge.swift @@ -0,0 +1,44 @@ +// +// ArbitrumBridge.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 04.10.2021. +// + +import UIKit + +typealias BridgeTokenURLProviderType = BuyTokenURLProviderType + +final class ArbitrumBridge: TokenActionsProvider, BridgeTokenURLProviderType { + private static let supportedServer: RPCServer = .main + + func isSupport(token: TokenActionsServiceKey) -> Bool { + switch token.type { + case .erc1155, .erc721, .erc721ForTickets, .erc875: + return false + case .nativeCryptocurrency, .erc20: + //NOTE: we are not pretty sure what tokens it supports, so let assume for all + return token.server == ArbitrumBridge.supportedServer + } + } + + func actions(token: TokenActionsServiceKey) -> [TokenInstanceAction] { + return [.init(type: .bridge(service: self))] + } + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { + return ArbitrumBridge.supportedServer + } + + var action: String { + return R.string.localizable.aWalletTokenArbitrumBridgeButtonTitle() + } + + var analyticsName: String { + "Arbitrum Bridge" + } + + func url(token: TokenActionsServiceKey) -> URL? { + return Constants.arbitrumBridge + } +} diff --git a/AlphaWallet/Core/SwapToken/HoneySwap/HoneySwap.swift b/AlphaWallet/Core/SwapToken/HoneySwap/HoneySwap.swift index e7ae3c55f..c26d6d758 100644 --- a/AlphaWallet/Core/SwapToken/HoneySwap/HoneySwap.swift +++ b/AlphaWallet/Core/SwapToken/HoneySwap/HoneySwap.swift @@ -13,7 +13,7 @@ class HoneySwap: TokenActionsProvider, SwapTokenURLProviderType { return R.string.localizable.aWalletTokenErc20ExchangeHoneyswapButtonTitle() } //NOTE: While selection on action browser will be automatically switched to defined server `rpcServer` - var rpcServer: RPCServer? { + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { return .xDai } @@ -97,7 +97,7 @@ class HoneySwap: TokenActionsProvider, SwapTokenURLProviderType { switch token.server { case .xDai: return true - case .main, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .custom, .poa, .callisto, .classic, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .custom, .poa, .callisto, .classic, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return false } } diff --git a/AlphaWallet/Core/SwapToken/Oneinch/Oneinch.swift b/AlphaWallet/Core/SwapToken/Oneinch/Oneinch.swift index ebb9da699..54ddb7c9c 100644 --- a/AlphaWallet/Core/SwapToken/Oneinch/Oneinch.swift +++ b/AlphaWallet/Core/SwapToken/Oneinch/Oneinch.swift @@ -14,8 +14,16 @@ class Oneinch: TokenActionsProvider, SwapTokenURLProviderType { var action: String { return R.string.localizable.aWalletTokenErc20ExchangeOn1inchButtonTitle() } - var rpcServer: RPCServer? { - .main + private var supportedServers: [RPCServer] { + return [.main, .binance_smart_chain, .polygon, .optimistic, .arbitrum] + } + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { + if supportedServers.contains(where: { $0 == token.server }) { + return token.server + } else { + return .main + } } var analyticsName: String { @@ -55,7 +63,7 @@ class Oneinch: TokenActionsProvider, SwapTokenURLProviderType { func isSupport(token: TokenActionsServiceKey) -> Bool { switch token.server { - case .main: + case .main, .arbitrum: return availableTokens[token.contractAddress] != nil case .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .custom, .poa, .callisto, .xDai, .classic, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: return false diff --git a/AlphaWallet/Core/SwapToken/QuickSwap/QuickSwap.swift b/AlphaWallet/Core/SwapToken/QuickSwap/QuickSwap.swift index 23f2b7528..41dbd3515 100644 --- a/AlphaWallet/Core/SwapToken/QuickSwap/QuickSwap.swift +++ b/AlphaWallet/Core/SwapToken/QuickSwap/QuickSwap.swift @@ -12,8 +12,9 @@ struct QuickSwap: TokenActionsProvider, SwapTokenURLProviderType { var action: String { return R.string.localizable.aWalletTokenErc20ExchangeOnQuickSwapButtonTitle() } - var rpcServer: RPCServer? { - .polygon + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { + return .polygon } var analyticsName: String { @@ -101,7 +102,7 @@ struct QuickSwap: TokenActionsProvider, SwapTokenURLProviderType { switch token.server { case .polygon: return true - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .xDai, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .xDai, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return false } } diff --git a/AlphaWallet/Core/SwapToken/SwapTokenService.swift b/AlphaWallet/Core/SwapToken/SwapTokenService.swift index 97a09b62d..c6902460c 100644 --- a/AlphaWallet/Core/SwapToken/SwapTokenService.swift +++ b/AlphaWallet/Core/SwapToken/SwapTokenService.swift @@ -12,12 +12,14 @@ struct TokenActionsServiceKey { let server: RPCServer var symbol: String var decimals: Int + let type: TokenType init(tokenObject: TokenObject) { self.contractAddress = tokenObject.contractAddress self.server = tokenObject.server self.symbol = tokenObject.symbol self.decimals = tokenObject.decimals + self.type = tokenObject.type } } @@ -28,8 +30,9 @@ protocol TokenActionsProvider { protocol SwapTokenURLProviderType { var action: String { get } - var rpcServer: RPCServer? { get } var analyticsName: String { get } + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? func url(token: TokenActionsServiceKey) -> URL? } diff --git a/AlphaWallet/Core/SwapToken/Uniswap/Uniswap.swift b/AlphaWallet/Core/SwapToken/Uniswap/Uniswap.swift index 4351ea163..98d18b8fb 100644 --- a/AlphaWallet/Core/SwapToken/Uniswap/Uniswap.swift +++ b/AlphaWallet/Core/SwapToken/Uniswap/Uniswap.swift @@ -12,8 +12,9 @@ struct Uniswap: TokenActionsProvider, SwapTokenURLProviderType { var action: String { return R.string.localizable.aWalletTokenErc20ExchangeOnUniswapButtonTitle() } - var rpcServer: RPCServer? { - .main + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { + return .main } var analyticsName: String { diff --git a/AlphaWallet/Core/SwapToken/Uniswap/UniswapERC20Token.swift b/AlphaWallet/Core/SwapToken/Uniswap/UniswapERC20Token.swift index a05980ece..b9c763d70 100644 --- a/AlphaWallet/Core/SwapToken/Uniswap/UniswapERC20Token.swift +++ b/AlphaWallet/Core/SwapToken/Uniswap/UniswapERC20Token.swift @@ -19,7 +19,7 @@ extension UniswapERC20Token { switch token.server { case .main: return availableTokens.contains(where: { $0.contract.sameContract(as: token.contractAddress) }) - case .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .custom, .poa, .callisto, .xDai, .classic, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .custom, .poa, .callisto, .xDai, .classic, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return false } } diff --git a/AlphaWallet/Core/SwapToken/xDaiBridge.swift b/AlphaWallet/Core/SwapToken/xDaiBridge.swift new file mode 100644 index 000000000..eb00f1565 --- /dev/null +++ b/AlphaWallet/Core/SwapToken/xDaiBridge.swift @@ -0,0 +1,41 @@ +// +// xDaiBridge.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 04.10.2021. +// + +import UIKit + +final class xDaiBridge: TokenActionsProvider, BridgeTokenURLProviderType { + private static let supportedServer: RPCServer = .xDai + + func isSupport(token: TokenActionsServiceKey) -> Bool { + switch token.type { + case .erc1155, .erc20, .erc721, .erc721ForTickets, .erc875: + return false + case .nativeCryptocurrency: + return token.server == xDaiBridge.supportedServer + } + } + + func actions(token: TokenActionsServiceKey) -> [TokenInstanceAction] { + return [.init(type: .bridge(service: self))] + } + + func rpcServer(forToken token: TokenActionsServiceKey) -> RPCServer? { + return xDaiBridge.supportedServer + } + + var action: String { + return R.string.localizable.aWalletTokenXDaiBridgeButtonTitle() + } + + var analyticsName: String { + "xDai Bridge" + } + + func url(token: TokenActionsServiceKey) -> URL? { + return Constants.xDaiBridge + } +} diff --git a/AlphaWallet/Core/Types/NativecryptoBalanceViewModel.swift b/AlphaWallet/Core/Types/NativecryptoBalanceViewModel.swift index eafa91034..3cd4bd9b0 100644 --- a/AlphaWallet/Core/Types/NativecryptoBalanceViewModel.swift +++ b/AlphaWallet/Core/Types/NativecryptoBalanceViewModel.swift @@ -44,7 +44,7 @@ struct NativecryptoBalanceViewModel: BalanceBaseViewModel { var currencyAmountWithoutSymbol: Double? { guard let rate = ticker?.rate else { return nil } - let symbol = mapSymbolToVersionInRates(server.symbol.lowercased()) + let symbol = mapSymbolToVersionInRates(server) guard let currentRate = (rate.rates.filter { $0.code == symbol }.first), currentRate.price > 0, amount > 0 else { return nil } return amount * currentRate.price @@ -62,8 +62,10 @@ struct NativecryptoBalanceViewModel: BalanceBaseViewModel { return server.symbol } - private func mapSymbolToVersionInRates(_ symbol: String) -> String { - let mapping = ["xdai": "dai"] + private func mapSymbolToVersionInRates(_ server: RPCServer) -> String { + let symbol = server.symbol.lowercased() + let mapping = ["xdai": "dai", "aeth": "eth"] + return mapping[symbol] ?? symbol } } diff --git a/AlphaWallet/Core/Types/TokenProviderType.swift b/AlphaWallet/Core/Types/TokenProviderType.swift index c68bb599f..50f1deddd 100644 --- a/AlphaWallet/Core/Types/TokenProviderType.swift +++ b/AlphaWallet/Core/Types/TokenProviderType.swift @@ -408,7 +408,9 @@ class TokenProvider: TokenProviderType { case .notErc721: break } - }.cauterize() + }.catch({ e in + error(value: e, pref: "isErc721Promise", address: address) + }) firstly { isErc875Promise @@ -418,7 +420,9 @@ class TokenProvider: TokenProviderType { } else { //no-op } - }.cauterize() + }.catch({ e in + error(value: e, pref: "isErc875Promise", address: address) + }) firstly { isErc1155Promise @@ -428,7 +432,9 @@ class TokenProvider: TokenProviderType { } else { //no-op } - }.cauterize() + }.catch({ e in + error(value: e, pref: "isErc1155Promise", address: address) + }) firstly { when(fulfilled: isErc875Promise.asVoid(), isErc721Promise.asVoid(), isErc1155Promise.asVoid()) @@ -438,7 +444,9 @@ class TokenProvider: TokenProviderType { } else { //no-op } - }.cauterize() + }.catch({ e in + error(value: e, pref: "isErc20Promise", address: address) + }) } } diff --git a/AlphaWallet/EtherClient/OpenSea.swift b/AlphaWallet/EtherClient/OpenSea.swift index e2dc3426f..ef28ebe5e 100644 --- a/AlphaWallet/EtherClient/OpenSea.swift +++ b/AlphaWallet/EtherClient/OpenSea.swift @@ -59,7 +59,7 @@ class OpenSea { switch server { case .main, .rinkeby: return true - case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .custom, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .custom, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return false } } @@ -110,7 +110,7 @@ class OpenSea { return Constants.openseaAPI case .rinkeby: return Constants.openseaRinkebyAPI - case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return Constants.openseaAPI } } diff --git a/AlphaWallet/EtherClient/TrustClient/AlphaWalletService.swift b/AlphaWallet/EtherClient/TrustClient/AlphaWalletService.swift index d6b76c282..e40626c83 100644 --- a/AlphaWallet/EtherClient/TrustClient/AlphaWalletService.swift +++ b/AlphaWallet/EtherClient/TrustClient/AlphaWalletService.swift @@ -57,7 +57,7 @@ extension AlphaWalletService: TargetType { switch self { case .getTransactions(_, let server, _, _, _, _): switch server { - case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return "" } case .register: @@ -153,7 +153,7 @@ extension AlphaWalletService: TargetType { switch self { case .getTransactions(_, let server, _, _, _, _): switch server { - case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return [ "Content-type": "application/json", "client": Bundle.main.bundleIdentifier ?? "", diff --git a/AlphaWallet/EtherClient/TrustClient/Models/ArrayResponse.swift b/AlphaWallet/EtherClient/TrustClient/Models/ArrayResponse.swift index 4d0c340ea..a10ddfb25 100644 --- a/AlphaWallet/EtherClient/TrustClient/Models/ArrayResponse.swift +++ b/AlphaWallet/EtherClient/TrustClient/Models/ArrayResponse.swift @@ -3,5 +3,21 @@ import Foundation struct ArrayResponse: Decodable { + private enum CodingKeys: CodingKey { + case result + } let result: [T] + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + result = try container.decode([T].self, forKey: .result) + } catch let e { + if case DecodingError.typeMismatch(_, _) = e { + result = [] + } else { + throw e + } + } + } } diff --git a/AlphaWallet/Gas/Models/GasNowGasPriceEstimator.swift b/AlphaWallet/Gas/Models/GasNowGasPriceEstimator.swift index 4029ec7b1..e2989a9c7 100644 --- a/AlphaWallet/Gas/Models/GasNowGasPriceEstimator.swift +++ b/AlphaWallet/Gas/Models/GasNowGasPriceEstimator.swift @@ -46,7 +46,7 @@ fileprivate extension RPCServer { switch self { case .main, .kovan, .ropsten, .rinkeby, .goerli, .binance_smart_chain, .heco, .polygon, .optimistic, .optimisticKovan: return etherscanApiRoot?.appendingQueryString("\("module=gastracker&action=gasoracle")\(apiKeyParameter)") - case .artis_sigma1, .artis_tau1, .binance_smart_chain_testnet, .callisto, .poa, .sokol, .classic, .xDai, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .mumbai_testnet, .cronosTestnet, .custom: + case .artis_sigma1, .artis_tau1, .binance_smart_chain_testnet, .callisto, .poa, .sokol, .classic, .xDai, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .mumbai_testnet, .cronosTestnet, .custom, .arbitrum: return nil } } diff --git a/AlphaWallet/InCoordinator.swift b/AlphaWallet/InCoordinator.swift index f05965bbd..6f1c8fb0b 100644 --- a/AlphaWallet/InCoordinator.swift +++ b/AlphaWallet/InCoordinator.swift @@ -125,6 +125,8 @@ class InCoordinator: NSObject, Coordinator { quickSwap.theme = navigationController.traitCollection.uniswapTheme service.register(service: quickSwap) + service.register(service: ArbitrumBridge()) + service.register(service: xDaiBridge()) return service }() @@ -967,7 +969,7 @@ extension InCoordinator: TokensCoordinatorDelegate { logTappedSwap(service: service) guard let token = transactionType.swapServiceInputToken, let url = service.url(token: token) else { return } - if let server = service.rpcServer { + if let server = service.rpcServer(forToken: token) { open(url: url, onServer: server) } else { open(for: url) diff --git a/AlphaWallet/Localization/en.lproj/Localizable.strings b/AlphaWallet/Localization/en.lproj/Localizable.strings index 03efcdf27..60e4b4d10 100644 --- a/AlphaWallet/Localization/en.lproj/Localizable.strings +++ b/AlphaWallet/Localization/en.lproj/Localizable.strings @@ -444,6 +444,7 @@ "blockchain.Optimistic" = "Optimistic Testnet"; "blockchain.Optimistic.Kovan" = "Optimistic Kovan Testnet"; "blockchain.Cronos.Testnet" = "Cronos Testnet"; +"blockchain.arbitrum" = "Arbitrum"; "photos" = "Browse"; "light" = "Light"; "qrCode.title" = "Point your camera on QR code"; @@ -531,6 +532,7 @@ "a.wallet.token.erc20ExchangeOn1inch.button.title" = "Swap"; "a.wallet.token.erc20ExchangeHoneyswap.button.title" = "Swap"; "a.wallet.token.xDaiBridge.button.title" = "Convert to DAI"; +"a.wallet.token.arbitrumBridge.button.title" = "Convert to Arbitrum"; "a.wallet.token.buy.xDai.title" = "Buy xDai"; "qrCode.myqrCode.title" = "My QR Code"; "qrCode.sendToAddress.title" = "Send to this Address"; diff --git a/AlphaWallet/Localization/es.lproj/Localizable.strings b/AlphaWallet/Localization/es.lproj/Localizable.strings index 4f3dd4fa1..4c1980520 100644 --- a/AlphaWallet/Localization/es.lproj/Localizable.strings +++ b/AlphaWallet/Localization/es.lproj/Localizable.strings @@ -444,6 +444,7 @@ "blockchain.Optimistic" = "Optimistic Testnet"; "blockchain.Optimistic.Kovan" = "Optimistic Kovan Testnet"; "blockchain.Cronos.Testnet" = "Cronos Testnet"; +"blockchain.arbitrum" = "Arbitrum"; "photos" = "Explorar"; "light" = "Luz"; "qrCode.title" = "Apunta la cámara al código QR"; @@ -531,6 +532,7 @@ "a.wallet.token.erc20ExchangeOn1inch.button.title" = "Swap"; "a.wallet.token.erc20ExchangeHoneyswap.button.title" = "Swap"; "a.wallet.token.xDaiBridge.button.title" = "Convert to DAI"; +"a.wallet.token.arbitrumBridge.button.title" = "Convert to Arbitrum"; "a.wallet.token.buy.xDai.title" = "Buy xDai"; "qrCode.myqrCode.title" = "My QR Code"; "qrCode.sendToAddress.title" = "Send to this Address"; diff --git a/AlphaWallet/Localization/ja.lproj/Localizable.strings b/AlphaWallet/Localization/ja.lproj/Localizable.strings index c8702fe4f..b6d73bebd 100644 --- a/AlphaWallet/Localization/ja.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ja.lproj/Localizable.strings @@ -442,6 +442,7 @@ "blockchain.Optimistic" = "Optimistic Testnet"; "blockchain.Optimistic.Kovan" = "Optimistic Kovan Testnet"; "blockchain.Cronos.Testnet" = "Cronos Testnet"; +"blockchain.arbitrum" = "Arbitrum"; "a.claim.token.failed.notEnoughXDAI.title" = "a.claim.token.failed.notEnoughXDAI.title"; "photos" = "Browse"; "light" = "Light"; @@ -531,6 +532,7 @@ "a.wallet.token.erc20ExchangeOn1inch.button.title" = "Swap"; "a.wallet.token.erc20ExchangeHoneyswap.button.title" = "Swap"; "a.wallet.token.xDaiBridge.button.title" = "Convert to DAI"; +"a.wallet.token.arbitrumBridge.button.title" = "Convert to Arbitrum"; "a.wallet.token.buy.xDai.title" = "Buy xDai"; "qrCode.myqrCode.title" = "My QR Code"; "qrCode.sendToAddress.title" = "Send to this Address"; diff --git a/AlphaWallet/Localization/ko.lproj/Localizable.strings b/AlphaWallet/Localization/ko.lproj/Localizable.strings index e23592f24..01693e245 100644 --- a/AlphaWallet/Localization/ko.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ko.lproj/Localizable.strings @@ -441,6 +441,7 @@ "blockchain.Optimistic" = "Optimistic Testnet"; "blockchain.Optimistic.Kovan" = "Optimistic Kovan Testnet"; "blockchain.Cronos.Testnet" = "Cronos Testnet"; +"blockchain.arbitrum" = "Arbitrum"; "a.claim.token.failed.notEnoughXDAI.title" = "a.claim.token.failed.notEnoughXDAI.title"; "photos" = "Browse"; "light" = "Light"; @@ -531,6 +532,7 @@ "a.wallet.token.erc20ExchangeOn1inch.button.title" = "Swap"; "a.wallet.token.erc20ExchangeHoneyswap.button.title" = "Swap"; "a.wallet.token.xDaiBridge.button.title" = "Convert to DAI"; +"a.wallet.token.arbitrumBridge.button.title" = "Convert to Arbitrum"; "a.wallet.token.buy.xDai.title" = "Buy xDai"; "qrCode.myqrCode.title" = "My QR Code"; "qrCode.sendToAddress.title" = "Send to this Address"; diff --git a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings index 98b5e5509..a8ef9a003 100644 --- a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings +++ b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings @@ -444,6 +444,7 @@ "blockchain.Optimistic" = "Optimistic Testnet"; "blockchain.Optimistic.Kovan" = "Optimistic Kovan Testnet"; "blockchain.Cronos.Testnet" = "Cronos Testnet"; +"blockchain.arbitrum" = "Arbitrum"; "photos" = "浏览"; "light" = "闪光灯"; "qrCode.title" = "将相机对准二维码"; @@ -531,6 +532,7 @@ "a.wallet.token.erc20ExchangeOn1inch.button.title" = "Swap"; "a.wallet.token.erc20ExchangeHoneyswap.button.title" = "Swap"; "a.wallet.token.xDaiBridge.button.title" = "Convert to DAI"; +"a.wallet.token.arbitrumBridge.button.title" = "Convert to Arbitrum"; "a.wallet.token.buy.xDai.title" = "Buy xDai"; "qrCode.myqrCode.title" = "My QR Code"; "qrCode.sendToAddress.title" = "Send to this Address"; diff --git a/AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift b/AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift index 3e3329635..0541c48d6 100644 --- a/AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift +++ b/AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift @@ -84,6 +84,8 @@ class UniversalLinkCoordinator: Coordinator { case .custom(let custom): //TODO better defaults or handling for when properties of custom chain is not provided by user return custom.symbol ?? "ETH" + case .arbitrum: + return "AETH" } } @@ -454,7 +456,7 @@ class UniversalLinkCoordinator: Coordinator { switch server { case .xDai: errorMessage = R.string.localizable.aClaimTokenFailedNotEnoughXDAITitle() - case .classic, .main, .poa, .callisto, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .classic, .main, .poa, .callisto, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: errorMessage = R.string.localizable.aClaimTokenFailedNotEnoughEthTitle() } if ethPrice.value == nil { diff --git a/AlphaWallet/Settings/Coordinators/ServersCoordinator.swift b/AlphaWallet/Settings/Coordinators/ServersCoordinator.swift index 9d722a0a0..8ec3822fc 100644 --- a/AlphaWallet/Settings/Coordinators/ServersCoordinator.swift +++ b/AlphaWallet/Settings/Coordinators/ServersCoordinator.swift @@ -37,6 +37,7 @@ class ServersCoordinator: Coordinator { .optimistic, .optimisticKovan, .cronosTestnet, + .arbitrum ] + RPCServer.customServers } diff --git a/AlphaWallet/Settings/Types/ConfigExplorer.swift b/AlphaWallet/Settings/Types/ConfigExplorer.swift index e39a35884..cb45a43bf 100644 --- a/AlphaWallet/Settings/Types/ConfigExplorer.swift +++ b/AlphaWallet/Settings/Types/ConfigExplorer.swift @@ -11,12 +11,12 @@ struct ConfigExplorer { self.server = server } - func transactionURL(for ID: String) -> (url: URL, name: String?)? { + func transactionURL(for ID: String) -> (url: URL, name: String)? { let result = explorer(for: server) guard let endpoint = result.url else { return .none } let urlString: String? = { switch server { - case .main, .kovan, .ropsten, .rinkeby, .sokol, .classic, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .callisto, .poa, .cronosTestnet, .custom: + case .main, .kovan, .ropsten, .rinkeby, .sokol, .classic, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .callisto, .poa, .cronosTestnet, .custom, .arbitrum: return endpoint + "/tx/" + ID } }() @@ -25,11 +25,11 @@ struct ConfigExplorer { return (url: url, name: result.name) } - func explorerName(for server: RPCServer) -> String? { + func explorerName(for server: RPCServer) -> String { switch server { case .main, .kovan, .ropsten, .rinkeby, .goerli: return "Etherscan" - case .classic, .poa, .custom, .callisto, .sokol, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .classic, .poa, .custom, .callisto, .sokol, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return "\(server.name) Explorer" case .xDai: return "Blockscout" @@ -38,7 +38,7 @@ struct ConfigExplorer { } } - private func explorer(for server: RPCServer) -> (url: String?, name: String?) { + private func explorer(for server: RPCServer) -> (url: String?, name: String) { let nameForServer = explorerName(for: server) let url = server.etherscanWebpageRoot return (url?.absoluteString, nameForServer) diff --git a/AlphaWallet/Settings/Types/Constants.swift b/AlphaWallet/Settings/Types/Constants.swift index 8f6f34d54..6d9cdbcef 100644 --- a/AlphaWallet/Settings/Types/Constants.swift +++ b/AlphaWallet/Settings/Types/Constants.swift @@ -37,6 +37,8 @@ public struct Constants { public static let optimisticMagicLinkHost = "optimistic.aw.app" public static let optimisticTestMagicLinkHost = "optimistic-kovan.aw.app" public static let cronosTestMagicLinkHost = "test-cronos.aw.app" + public static let arbitrumMagicLinkHost = "arbitrum.aw.app" + public enum Currency { static let usd = "USD" @@ -87,6 +89,7 @@ public struct Constants { //xDai dapps static let xDaiBridge = URL(string: "https://bridge.xdaichain.com/")! + static let arbitrumBridge = URL(string: "https://bridge.arbitrum.io/")! static let buyXDaiWitRampUrl = "https://buy.ramp.network/?hostApiKey=\(Constants.Credentials.rampApiKey)&hostLogoUrl=https%3A%2F%2Falphawallet.com%2Fwp-content%2Fthemes%2Falphawallet%2Fimg%2Falphawallet-logo.svg&hostAppName=AlphaWallet&swapAsset=xDai" static func buyWitRampUrl(asset: String) -> String { @@ -172,7 +175,7 @@ public struct Constants { static let ensContractOnMainnet = AlphaWallet.Address.ethereumAddress(eip55String: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85") - static let defaultEnabledServers: [RPCServer] = [.main, .xDai, .polygon] + static let defaultEnabledServers: [RPCServer] = [.arbitrum]//[.main, .xDai, .polygon] static let defaultEnabledTestnetServers: [RPCServer] = [.ropsten] static let tokenScriptUrlSchemeForResources = "tokenscript-resource:///" diff --git a/AlphaWallet/Settings/Types/RPCServers.swift b/AlphaWallet/Settings/Types/RPCServers.swift index fe4973d50..def5fc5e7 100644 --- a/AlphaWallet/Settings/Types/RPCServers.swift +++ b/AlphaWallet/Settings/Types/RPCServers.swift @@ -47,6 +47,7 @@ enum RPCServer: Hashable, CaseIterable { case optimisticKovan case cronosTestnet case custom(CustomRPC) + case arbitrum enum EtherscanCompatibleType: String, Codable { case etherscan @@ -88,6 +89,7 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return 10 case .optimisticKovan: return 69 case .cronosTestnet: return 338 + case .arbitrum: return 42161 } } @@ -119,12 +121,13 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return "Optimistic Ethereum" case .optimisticKovan: return "Optimistic Kovan" case .cronosTestnet: return "Cronos Testnet" + case .arbitrum: return "Arbitrum One" } } var isTestnet: Bool { switch self { - case .xDai, .classic, .main, .poa, .callisto, .binance_smart_chain, .artis_sigma1, .heco, .fantom, .avalanche, .polygon, .optimistic: + case .xDai, .classic, .main, .poa, .callisto, .binance_smart_chain, .artis_sigma1, .heco, .fantom, .avalanche, .polygon, .optimistic, .arbitrum: return false case .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_tau1, .binance_smart_chain_testnet, .heco_testnet, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .optimisticKovan, .cronosTestnet: return true @@ -135,7 +138,7 @@ enum RPCServer: Hashable, CaseIterable { var customRpc: CustomRPC? { switch self { - case .xDai, .classic, .main, .poa, .callisto, .binance_smart_chain, .artis_sigma1, .heco, .fantom, .avalanche, .polygon, .optimistic, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_tau1, .binance_smart_chain_testnet, .heco_testnet, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .optimisticKovan, .cronosTestnet: + case .xDai, .classic, .main, .poa, .callisto, .binance_smart_chain, .artis_sigma1, .heco, .fantom, .avalanche, .polygon, .optimistic, .kovan, .ropsten, .rinkeby, .sokol, .goerli, .artis_tau1, .binance_smart_chain_testnet, .heco_testnet, .fantom_testnet, .avalanche_testnet, .mumbai_testnet, .optimisticKovan, .cronosTestnet, .arbitrum: return nil case .custom(let custom): return custom @@ -148,7 +151,7 @@ enum RPCServer: Hashable, CaseIterable { var etherscanURLForGeneralTransactionHistory: URL? { switch self { - case .main, .ropsten, .rinkeby, .kovan, .poa, .classic, .goerli, .xDai, .artis_sigma1, .artis_tau1, .polygon, .binance_smart_chain, .binance_smart_chain_testnet, .sokol, .callisto, .optimistic, .optimisticKovan, .cronosTestnet, .custom: + case .main, .ropsten, .rinkeby, .kovan, .poa, .classic, .goerli, .xDai, .artis_sigma1, .artis_tau1, .polygon, .binance_smart_chain, .binance_smart_chain_testnet, .sokol, .callisto, .optimistic, .optimisticKovan, .cronosTestnet, .custom, .arbitrum: return etherscanApiRoot?.appendingQueryString("module=account&action=txlist") case .heco: return nil case .heco_testnet: return nil @@ -197,8 +200,8 @@ enum RPCServer: Hashable, CaseIterable { case .optimisticKovan: return "https://kovan-optimistic.etherscan.io" case .cronosTestnet: return "https://cronos-explorer.crypto.org" case .custom: return nil - case .fantom_testnet, .avalanche, .avalanche_testnet: - return nil + case .fantom_testnet, .avalanche, .avalanche_testnet: return nil + case .arbitrum: return "https://arbiscan.io" } }() return urlString.flatMap { URL(string: $0) } @@ -239,6 +242,7 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return "https://api-optimistic.etherscan.io/api" case .optimisticKovan: return "https://api-kovan-optimistic.etherscan.io/api" case .cronosTestnet: return "https://cronos-explorer.crypto.org/api" + case .arbitrum: return "https://api.arbiscan.io/api" } }() return urlString.flatMap { URL(string: $0) } @@ -258,7 +262,7 @@ enum RPCServer: Hashable, CaseIterable { private var etherscanCompatibleType: EtherscanCompatibleType { switch self { - case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .optimistic, .optimisticKovan, .binance_smart_chain, .binance_smart_chain_testnet, .polygon: + case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .optimistic, .optimisticKovan, .binance_smart_chain, .binance_smart_chain_testnet, .polygon, .arbitrum: return .etherscan case .poa, .sokol, .classic, .xDai, .artis_sigma1, .artis_tau1, .mumbai_testnet, .callisto, .cronosTestnet: return .blockscout @@ -271,7 +275,7 @@ enum RPCServer: Hashable, CaseIterable { var etherscanApiKey: String? { switch self { - case .main, .kovan, .ropsten, .rinkeby, .goerli, .optimistic, .optimisticKovan: + case .main, .kovan, .ropsten, .rinkeby, .goerli, .optimistic, .optimisticKovan, .arbitrum: return Constants.Credentials.etherscanKey case .binance_smart_chain: //Key not needed for testnet (empirically) @@ -288,7 +292,7 @@ enum RPCServer: Hashable, CaseIterable { switch self { case .optimistic, .optimisticKovan: return AlphaWallet.Address(string: "0x4200000000000000000000000000000000000006")! - case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .binance_smart_chain, .binance_smart_chain_testnet, .polygon, .poa, .sokol, .classic, .xDai, .artis_sigma1, .artis_tau1, .mumbai_testnet, .callisto, .cronosTestnet, .fantom_testnet, .avalanche, .avalanche_testnet, .custom: + case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .binance_smart_chain, .binance_smart_chain_testnet, .polygon, .poa, .sokol, .classic, .xDai, .artis_sigma1, .artis_tau1, .mumbai_testnet, .callisto, .cronosTestnet, .fantom_testnet, .avalanche, .avalanche_testnet, .custom, .arbitrum: return nil } } @@ -296,7 +300,7 @@ enum RPCServer: Hashable, CaseIterable { //Optimistic don't allow changing the gas price and limit var canUserChangeGas: Bool { switch self { - case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .binance_smart_chain, .binance_smart_chain_testnet, .polygon, .poa, .sokol, .classic, .xDai, .artis_sigma1, .artis_tau1, .mumbai_testnet, .callisto, .cronosTestnet, .fantom_testnet, .avalanche, .avalanche_testnet, .custom: + case .main, .ropsten, .rinkeby, .kovan, .goerli, .fantom, .heco, .heco_testnet, .binance_smart_chain, .binance_smart_chain_testnet, .polygon, .poa, .sokol, .classic, .xDai, .artis_sigma1, .artis_tau1, .mumbai_testnet, .callisto, .cronosTestnet, .fantom_testnet, .avalanche, .avalanche_testnet, .custom, .arbitrum: return true case .optimistic, .optimisticKovan: return false @@ -373,14 +377,14 @@ enum RPCServer: Hashable, CaseIterable { switch self { case .main: return etherscanWebpageRoot?.appendingPathComponent("token").appendingPathComponent(address.eip55String) - case .ropsten, .rinkeby, .kovan, .xDai, .goerli, .poa, .sokol, .classic, .callisto, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .ropsten, .rinkeby, .kovan, .xDai, .goerli, .poa, .sokol, .classic, .callisto, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return etherscanContractDetailsWebPageURL(for: address) } } var priceID: AlphaWallet.Address { switch self { - case .main, .ropsten, .rinkeby, .kovan, .sokol, .custom, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .ropsten, .rinkeby, .kovan, .sokol, .custom, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return AlphaWallet.Address(string: "0x000000000000000000000000000000000000003c")! case .poa: return AlphaWallet.Address(string: "0x00000000000000000000000000000000000000AC")! @@ -418,6 +422,7 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return "ETH" case .optimisticKovan: return "ETH" case .cronosTestnet: return "tCRO" + case .arbitrum: return "AETH" } } @@ -443,6 +448,7 @@ enum RPCServer: Hashable, CaseIterable { return "tCRO" case .custom(let custom): return custom.nativeCryptoTokenName ?? "Ether" + case .arbitrum: return "AETH" } } @@ -456,7 +462,7 @@ enum RPCServer: Hashable, CaseIterable { case .kovan: return .Kovan case .ropsten: return .Ropsten case .rinkeby: return .Rinkeby - case .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .custom, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .custom, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return .Custom(networkID: BigUInt(chainID)) } } @@ -520,6 +526,8 @@ enum RPCServer: Hashable, CaseIterable { return Constants.optimisticTestMagicLinkHost case .cronosTestnet: return Constants.cronosTestMagicLinkHost + case .arbitrum: + return Constants.arbitrumMagicLinkHost } } @@ -552,6 +560,7 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return "https://mainnet.optimism.io" case .optimisticKovan: return "https://kovan.optimism.io" case .cronosTestnet: return "https://cronos-testnet.crypto.org:8545" + case .arbitrum: return "https://arbitrum-mainnet.infura.io/v3/\(Constants.Credentials.infuraKey)" } }() return URL(string: urlString)! @@ -559,7 +568,7 @@ enum RPCServer: Hashable, CaseIterable { var transactionInfoEndpoints: URL? { switch self { - case .main, .kovan, .ropsten, .rinkeby, .goerli, .classic, .poa, .xDai, .sokol, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .fantom, .polygon, .mumbai_testnet, .heco, .heco_testnet, .callisto, .optimistic, .optimisticKovan, .cronosTestnet, .custom: + case .main, .kovan, .ropsten, .rinkeby, .goerli, .classic, .poa, .xDai, .sokol, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .fantom, .polygon, .mumbai_testnet, .heco, .heco_testnet, .callisto, .optimistic, .optimisticKovan, .cronosTestnet, .custom, .arbitrum: return etherscanApiRoot case .fantom_testnet: return URL(string: "https://explorer.testnet.fantom.network/tx/") case .avalanche: return URL(string: "https://cchain.explorer.avax.network/tx/") @@ -573,14 +582,14 @@ enum RPCServer: Hashable, CaseIterable { case .ropsten: return Constants.ENSRegistrarRopsten case .rinkeby: return Constants.ENSRegistrarRinkeby case .goerli: return Constants.ENSRegistrarGoerli - case .xDai, .kovan, .poa, .sokol, .classic, .callisto, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .xDai, .kovan, .poa, .sokol, .classic, .callisto, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return Constants.ENSRegistrarAddress } } var endRecordsContract: AlphaWallet.Address { switch self { - case .main, .xDai, .kovan, .ropsten, .rinkeby, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .xDai, .kovan, .ropsten, .rinkeby, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return Constants.ENSRecordsContractAddress case .poa: return Constants.ENSRecordsContractAddressPOA @@ -591,7 +600,7 @@ enum RPCServer: Hashable, CaseIterable { switch self { case .main, .xDai: return .normal - case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return .low } } @@ -634,6 +643,8 @@ enum RPCServer: Hashable, CaseIterable { return R.string.localizable.blockchainCronosTestnet() case .custom(let custom): return custom.chainName + case .arbitrum: + return R.string.localizable.blockchainArbitrum() } } @@ -662,12 +673,13 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: return .red case .optimisticKovan: return .red case .cronosTestnet: return .red + case .arbitrum: return .red } } var transactionDataCoordinatorType: SingleChainTransactionDataCoordinator.Type { switch self { - case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .binance_smart_chain, .binance_smart_chain_testnet, .artis_tau1, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .classic, .callisto, .kovan, .ropsten, .custom, .rinkeby, .poa, .sokol, .goerli, .xDai, .artis_sigma1, .binance_smart_chain, .binance_smart_chain_testnet, .artis_tau1, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return SingleChainTransactionEtherscanDataCoordinator.self } } @@ -706,6 +718,8 @@ enum RPCServer: Hashable, CaseIterable { return R.image.iconsTokensOptimistic() case .optimisticKovan: return R.image.iconsTokensOptimisticKovan() + case .arbitrum: + return R.image.arbitrum() } } @@ -765,6 +779,7 @@ enum RPCServer: Hashable, CaseIterable { .optimistic, .optimisticKovan, .cronosTestnet, + .arbitrum ] } @@ -793,7 +808,7 @@ enum RPCServer: Hashable, CaseIterable { case .optimistic: //These not allow range more than 10000 return .blockNumber(fromBlockNumber + 9999) - case .polygon, .mumbai_testnet, .cronosTestnet: + case .polygon, .mumbai_testnet, .cronosTestnet, .arbitrum: //These not allow range more than 100000 return .blockNumber(fromBlockNumber + 99990) case .main, .kovan, .ropsten, .rinkeby, .poa, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .optimisticKovan, .sokol, .custom: diff --git a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift index ef0b8ec76..8385fab2a 100644 --- a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift +++ b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift @@ -126,6 +126,7 @@ extension EventSourceCoordinator.functional { }).done(on: queue, { _ in seal.fulfill(()) }).catch(on: queue, { e in + error(value: e, rpcServer: tokenServer, address: contractAddress) seal.reject(e) }) } diff --git a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift index 68b056410..569655698 100644 --- a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift +++ b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift @@ -150,6 +150,7 @@ extension EventSourceCoordinatorForActivities.functional { }).done { _ in seal.fulfill(()) }.catch { e in + error(value: e, rpcServer: server, address: tokenContract) seal.reject(e) } } @@ -190,4 +191,4 @@ extension EventSourceCoordinatorForActivities.functional { guard let filterValueTypedForEventFilters = filterValue.coerceToArgumentTypeForEventFilter(parameterType) else { return nil } return (filter: [filterValueTypedForEventFilters], textEquivalent: textEquivalent) } -} \ No newline at end of file +} diff --git a/AlphaWallet/Tokens/Coordinators/ENSReverseLookupCoordinator.swift b/AlphaWallet/Tokens/Coordinators/ENSReverseLookupCoordinator.swift index afa5f208c..48d2d0a86 100644 --- a/AlphaWallet/Tokens/Coordinators/ENSReverseLookupCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/ENSReverseLookupCoordinator.swift @@ -67,7 +67,6 @@ class ENSReverseLookupCoordinator { } } }.cauterize() - } } else { completion(.failure(AnyError(Web3Error(description: "Error extracting result from \(self.server.ensRegistrarContract).\(function.name)()")))) diff --git a/AlphaWallet/Tokens/Coordinators/GetERC20BalanceCoordinator.swift b/AlphaWallet/Tokens/Coordinators/GetERC20BalanceCoordinator.swift index 436ec1a04..bf8a73019 100644 --- a/AlphaWallet/Tokens/Coordinators/GetERC20BalanceCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/GetERC20BalanceCoordinator.swift @@ -14,7 +14,7 @@ class GetERC20BalanceCoordinator: CallbackQueueProvider { self.server = server self.queue = queue } - + func getBalance(for address: AlphaWallet.Address, contract: AlphaWallet.Address) -> Promise { return Promise { seal in getBalance(for: address, contract: contract) { result in diff --git a/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift b/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift index ed55ccba0..fa57cc002 100644 --- a/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift @@ -180,7 +180,7 @@ class SingleChainTokenCoordinator: Coordinator { autoDetectXDaiPartnerTokens() case .rinkeby: autoDetectRinkebyPartnerTokens() - case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .binance_smart_chain, .binance_smart_chain_testnet, .artis_tau1, .custom, .heco_testnet, .heco, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .binance_smart_chain, .binance_smart_chain_testnet, .artis_tau1, .custom, .heco_testnet, .heco, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: break } } @@ -677,7 +677,7 @@ extension SingleChainTokenCoordinator: TokenViewControllerDelegate { switch action.type { case .tokenScript: showTokenInstanceActionView(forAction: action, fungibleTokenObject: token, navigationController: navigationController) - case .erc20Send, .erc20Receive, .nftRedeem, .nftSell, .nonFungibleTransfer, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .nftRedeem, .nftSell, .nonFungibleTransfer, .swap, .buy, .bridge: //Couldn't have reached here break } diff --git a/AlphaWallet/Tokens/Types/TokenCollection.swift b/AlphaWallet/Tokens/Types/TokenCollection.swift index 7722a67e0..512f4fe76 100644 --- a/AlphaWallet/Tokens/Types/TokenCollection.swift +++ b/AlphaWallet/Tokens/Types/TokenCollection.swift @@ -127,6 +127,7 @@ extension RPCServer { case .optimistic: return 22 case .optimisticKovan: return 23 case .cronosTestnet: return 24 + case .arbitrum: return 25 } } } diff --git a/AlphaWallet/Tokens/Types/TokenInstanceAction.swift b/AlphaWallet/Tokens/Types/TokenInstanceAction.swift index 81e991c82..a9f52946a 100644 --- a/AlphaWallet/Tokens/Types/TokenInstanceAction.swift +++ b/AlphaWallet/Tokens/Types/TokenInstanceAction.swift @@ -13,7 +13,7 @@ struct TokenInstanceAction { case nonFungibleTransfer case tokenScript(contract: AlphaWallet.Address, title: String, viewHtml: (html: String, style: String), attributes: [AttributeId: AssetAttribute], transactionFunction: FunctionOrigin?, selection: TokenScriptSelection?) case swap(service: SwapTokenURLProviderType) - case xDaiBridge + case bridge(service: BridgeTokenURLProviderType) case buy(service: BuyTokenURLProviderType) } @@ -35,13 +35,13 @@ struct TokenInstanceAction { return service.action case .buy(let service): return service.action - case .xDaiBridge: - return R.string.localizable.aWalletTokenXDaiBridgeButtonTitle() + case .bridge(let service): + return service.action } } var attributes: [AttributeId: AssetAttribute] { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return .init() case .nftRedeem, .nftSell, .nonFungibleTransfer: return .init() @@ -71,7 +71,7 @@ struct TokenInstanceAction { } var transactionFunction: FunctionOrigin? { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return nil case .nftRedeem, .nftSell, .nonFungibleTransfer: return nil @@ -81,7 +81,7 @@ struct TokenInstanceAction { } var contract: AlphaWallet.Address? { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return nil case .nftRedeem, .nftSell, .nonFungibleTransfer: return nil @@ -97,7 +97,7 @@ struct TokenInstanceAction { //TODO we can live-reload the action view screen now if we observe for changes func viewHtml(forTokenHolder tokenHolder: TokenHolder, tokenId: TokenId) -> (html: String, hash: Int) { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return (html: "", hash: 0) case .nftRedeem: return (html: "", hash: 0) @@ -114,7 +114,7 @@ struct TokenInstanceAction { func activeExcludingSelection(selectedTokenHolders: [TokenHolder], forWalletAddress walletAddress: AlphaWallet.Address, fungibleBalance: BigInt? = nil) -> TokenScriptSelection? { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return nil case .nftRedeem, .nftSell, .nonFungibleTransfer: return nil @@ -134,7 +134,7 @@ struct TokenInstanceAction { func activeExcludingSelection(selectedTokenHolder tokenHolder: TokenHolder, tokenId: TokenId, forWalletAddress walletAddress: AlphaWallet.Address, fungibleBalance: BigInt? = nil) -> TokenScriptSelection? { switch type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: return nil case .nftRedeem, .nftSell, .nonFungibleTransfer: return nil diff --git a/AlphaWallet/Tokens/ViewControllers/Collectibles/ViewControllers/Erc1155TokenInstanceViewController.swift b/AlphaWallet/Tokens/ViewControllers/Collectibles/ViewControllers/Erc1155TokenInstanceViewController.swift index 3a3dc298e..25b3bdade 100644 --- a/AlphaWallet/Tokens/ViewControllers/Collectibles/ViewControllers/Erc1155TokenInstanceViewController.swift +++ b/AlphaWallet/Tokens/ViewControllers/Collectibles/ViewControllers/Erc1155TokenInstanceViewController.swift @@ -180,7 +180,7 @@ class Erc1155TokenInstanceViewController: UIViewController, TokenVerifiableStatu let actions = viewModel.actions for (action, button) in zip(actions, buttonsBar.buttons) where button == sender { switch action.type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: //TODO when we support TokenScript views for ERC20s, we need to perform the action here break case .nftRedeem: diff --git a/AlphaWallet/Tokens/ViewControllers/TokenInstanceViewController.swift b/AlphaWallet/Tokens/ViewControllers/TokenInstanceViewController.swift index 375935652..44037249b 100644 --- a/AlphaWallet/Tokens/ViewControllers/TokenInstanceViewController.swift +++ b/AlphaWallet/Tokens/ViewControllers/TokenInstanceViewController.swift @@ -152,7 +152,7 @@ class TokenInstanceViewController: UIViewController, TokenVerifiableStatusViewCo let actions = viewModel.actions for (action, button) in zip(actions, buttonsBar.buttons) where button == sender { switch action.type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: //TODO when we support TokenScript views for ERC20s, we need to perform the action here break case .nftRedeem: diff --git a/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift b/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift index 92ca7c2c0..92dafee85 100644 --- a/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift +++ b/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift @@ -256,20 +256,12 @@ class TokenViewController: UIViewController { } else { delegate?.didTap(action: action, transactionType: transactionType, viewController: self) } - case .xDaiBridge: - delegate?.shouldOpen(url: Constants.xDaiBridge, shouldSwitchServer: true, forTransactionType: transactionType, inViewController: self) - case .buy(let service): - var tokenObject: TokenActionsServiceKey? - switch transactionType { - case .nativeCryptocurrency(let token, _, _): - tokenObject = TokenActionsServiceKey(tokenObject: token) - case .erc20Token(let token, _, _): - tokenObject = TokenActionsServiceKey(tokenObject: token) - case .erc875Token, .erc875TokenOrder, .erc721Token, .erc721ForTicketToken, .erc1155Token, .dapp, .tokenScript, .claimPaidErc875MagicLink: - tokenObject = .none - } + case .bridge(let service): + guard let token = transactionType.swapServiceInputToken, let url = service.url(token: token) else { return } - guard let token = tokenObject, let url = service.url(token: token) else { return } + delegate?.shouldOpen(url: url, shouldSwitchServer: true, forTransactionType: transactionType, inViewController: self) + case .buy(let service): + guard let token = transactionType.swapServiceInputToken, let url = service.url(token: token) else { return } logStartOnRamp(name: "Ramp") delegate?.shouldOpen(url: url, shouldSwitchServer: false, forTransactionType: transactionType, inViewController: self) diff --git a/AlphaWallet/Tokens/ViewControllers/TokensCardViewController.swift b/AlphaWallet/Tokens/ViewControllers/TokensCardViewController.swift index 3b7cfb8e2..3de110aa6 100644 --- a/AlphaWallet/Tokens/ViewControllers/TokensCardViewController.swift +++ b/AlphaWallet/Tokens/ViewControllers/TokensCardViewController.swift @@ -216,7 +216,7 @@ class TokensCardViewController: UIViewController, TokenVerifiableStatusViewContr private func handle(action: TokenInstanceAction) { guard let tokenHolder = selectedTokenHolder else { return } switch action.type { - case .erc20Send, .erc20Receive, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .swap, .buy, .bridge: break case .nftRedeem: redeem() diff --git a/AlphaWallet/Tokens/ViewModels/FungibleTokenViewCellViewModel.swift b/AlphaWallet/Tokens/ViewModels/FungibleTokenViewCellViewModel.swift index e6171ddd4..896c28c70 100644 --- a/AlphaWallet/Tokens/ViewModels/FungibleTokenViewCellViewModel.swift +++ b/AlphaWallet/Tokens/ViewModels/FungibleTokenViewCellViewModel.swift @@ -71,7 +71,7 @@ struct FungibleTokenViewCellViewModel { return "-" } }() - + return NSAttributedString(string: apprecation24hours, attributes: [ .foregroundColor: valuePercentageChangeColor, .font: Screen.TokenCard.Font.valueChangeLabel @@ -103,7 +103,7 @@ struct FungibleTokenViewCellViewModel { .font: Screen.TokenCard.Font.valueChangeLabel ]) } - + private var fiatValue: String { if let fiatValue = EthCurrencyHelper(ticker: ticker).fiatValue(value: token.optionalDecimalValue) { return NumberFormatter.usd(format: .fiatFormat).string(from: fiatValue) ?? "-" diff --git a/AlphaWallet/Tokens/ViewModels/TokenViewControllerViewModel.swift b/AlphaWallet/Tokens/ViewModels/TokenViewControllerViewModel.swift index 5e19bb090..714072c06 100644 --- a/AlphaWallet/Tokens/ViewModels/TokenViewControllerViewModel.swift +++ b/AlphaWallet/Tokens/ViewModels/TokenViewControllerViewModel.swift @@ -27,7 +27,7 @@ struct TokenViewControllerViewModel { var actions: [TokenInstanceAction] { guard let token = token else { return [] } let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore) - var actionsFromTokenScript = xmlHandler.actions + let actionsFromTokenScript = xmlHandler.actions let key = TokenActionsServiceKey(tokenObject: token) if actionsFromTokenScript.isEmpty { @@ -54,8 +54,8 @@ struct TokenViewControllerViewModel { ] switch token.server { case .xDai: - return [.init(type: .erc20Send), .init(type: .xDaiBridge), .init(type: .erc20Receive)] + tokenActionsProvider.actions(token: key) - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + return [.init(type: .erc20Send), .init(type: .erc20Receive)] + tokenActionsProvider.actions(token: key) + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return actions + tokenActionsProvider.actions(token: key) } } @@ -66,18 +66,9 @@ struct TokenViewControllerViewModel { case .erc20: return actionsFromTokenScript + tokenActionsProvider.actions(token: key) case .nativeCryptocurrency: - let xDaiBridgeActions: [TokenInstanceAction] - switch token.server { - case .xDai: - xDaiBridgeActions = [.init(type: .xDaiBridge)] - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .custom, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: - xDaiBridgeActions = [] - } - - //TODO we should support retrieval of XML (and XMLHandler) based on address + server. For now, this is only important for native cryptocurrency. So might be ok to check like this for now +// TODO we should support retrieval of XML (and XMLHandler) based on address + server. For now, this is only important for native cryptocurrency. So might be ok to check like this for now if let server = xmlHandler.server, server.matches(server: token.server) { - actionsFromTokenScript += tokenActionsProvider.actions(token: key) - return xDaiBridgeActions + actionsFromTokenScript + return actionsFromTokenScript + tokenActionsProvider.actions(token: key) } else { //TODO .erc20Send and .erc20Receive names aren't appropriate let actions: [TokenInstanceAction] = [ @@ -85,7 +76,7 @@ struct TokenViewControllerViewModel { .init(type: .erc20Receive) ] - return xDaiBridgeActions + actions + tokenActionsProvider.actions(token: key) + return actions + tokenActionsProvider.actions(token: key) } } } diff --git a/AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift b/AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift index b4de42f0b..79444af4d 100644 --- a/AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift +++ b/AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift @@ -9,6 +9,7 @@ import Moya import PromiseKit import Result import UserNotifications +import Mixpanel class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionDataCoordinator { private let storage: TransactionsStorage @@ -115,7 +116,9 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData Config.setLastFetchedErc20InteractionBlockNumber(maxBlockNumber, server: server, wallet: wallet) } strongSelf.update(items: backFilledTransactions) - }.cauterize() + }.catch({ e in + error(value: e, function: #function, rpcServer: server, address: wallet) + }) .finally { [weak self] in self?.isAutoDetectingERC20Transactions = false } @@ -141,7 +144,9 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData Config.setLastFetchedErc721InteractionBlockNumber(maxBlockNumber, server: server, wallet: wallet) } strongSelf.update(items: backFilledTransactions) - }.cauterize() + }.catch({ e in + error(value: e, rpcServer: server, address: wallet) + }) .finally { [weak self] in self?.isAutoDetectingErc721Transactions = false } @@ -295,9 +300,10 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData }).cauterize() } - private func handleError(error: Error) { + private func handleError(error e: Error) { //delegate?.didUpdate(result: .failure(TransactionError.failedToFetch)) // Avoid showing an error on failed request, instead show cached transactions. + error(value: e) } //TODO notify user of received tokens too @@ -345,7 +351,7 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData } case .classic, .xDai: break - case .kovan, .ropsten, .rinkeby, .poa, .sokol, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .rinkeby, .poa, .sokol, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: break } } @@ -370,7 +376,7 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData switch session.server { case .main, .xDai: content.body = R.string.localizable.transactionsReceivedEther(amount, session.server.symbol) - case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: content.body = R.string.localizable.transactionsReceivedEther("\(amount) (\(session.server.name))", session.server.symbol) } content.sound = .default @@ -406,7 +412,9 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData strongSelf.transactionsTracker.fetchingState = .failed } - }).cauterize() + }).catch({ e in + error(value: e, rpcServer: self.session.server, address: address) + }) } func stop() { @@ -461,7 +469,7 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData }).done(on: queue, { transactions in coordinator.update(items: transactions) }).catch { e in - coordinator.handleError(error: e) + error(value: e, rpcServer: coordinator.session.server, address: self.session.account.address) }.finally { [weak self] in guard let strongSelf = self else { return } @@ -492,10 +500,12 @@ extension SingleChainTransactionEtherscanDataCoordinator.functional { } static func fetchTransactions(for address: AlphaWallet.Address, startBlock: Int, endBlock: Int = 999_999_999, sortOrder: AlphaWalletService.SortOrder, session: WalletSession, alphaWalletProvider: MoyaProvider, tokensStorage: TokensDataStore, tokenProvider: TokenProviderType, queue: DispatchQueue) -> Promise<[TransactionInstance]> { - firstly { - alphaWalletProvider.request(.getTransactions(config: session.config, server: session.server, address: address, startBlock: startBlock, endBlock: endBlock, sortOrder: sortOrder)) - }.map(on: queue) { - try $0.map(ArrayResponse.self).result.map { + let target: AlphaWalletService = .getTransactions(config: session.config, server: session.server, address: address, startBlock: startBlock, endBlock: endBlock, sortOrder: sortOrder) + + return firstly { + alphaWalletProvider.request(target) + }.map(on: queue) { response -> [Promise] in + return try response.map(ArrayResponse.self).result.map { TransactionInstance.from(transaction: $0, tokensStorage: tokensStorage, tokenProvider: tokenProvider) } }.then(on: queue) { @@ -526,3 +536,14 @@ extension SingleChainTransactionEtherscanDataCoordinator.functional { } } } + +func error(value e: Error, pref: String = "", function f: String = #function, rpcServer: RPCServer? = nil, address: AlphaWallet.Address? = nil) { + + var description = "\(f)" + description += pref.isEmpty ? "" : " \(pref)" + description += rpcServer.flatMap { " server: \($0)" } ?? "" + description += address.flatMap { " address: \($0.eip55String)" } ?? "" + description += " \(e)" + + error(description) +} diff --git a/AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift b/AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift index d15a2e5f7..5854f43a5 100644 --- a/AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift +++ b/AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift @@ -483,7 +483,7 @@ extension TokensCardCoordinator: TokensCardViewControllerDelegate { switch action.type { case .tokenScript: showTokenInstanceActionView(forAction: action, tokenHolder: tokenHolder, viewController: viewController) - case .erc20Send, .erc20Receive, .nftRedeem, .nftSell, .nonFungibleTransfer, .swap, .xDaiBridge, .buy: + case .erc20Send, .erc20Receive, .nftRedeem, .nftSell, .nonFungibleTransfer, .swap, .buy, .bridge: //Couldn't have reached here break } diff --git a/AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift b/AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift index 47789733c..fbf4f6a00 100644 --- a/AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift +++ b/AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift @@ -170,7 +170,9 @@ class TransactionConfigurator { } self.delegate?.gasLimitEstimateUpdated(to: gasLimit, in: self) - }.cauterize() + }.catch({ e in + error(value: e, rpcServer: self.session.server) + }) } private func estimateGasPrice() { @@ -196,7 +198,9 @@ class TransactionConfigurator { } self.delegate?.gasPriceEstimateUpdated(to: standard, in: self) - }.cauterize() + }.catch({ e in + error(value: e, rpcServer: self.session.server) + }) } private func shouldUseEstimatedGasPrice(_ estimatedGasPrice: BigInt) -> Bool { @@ -217,7 +221,7 @@ class TransactionConfigurator { switch server { case .xDai: return estimateGasPriceForXDai() - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return Promise(estimateGasPriceForUseRpcNode(server: server)) } } @@ -295,7 +299,7 @@ class TransactionConfigurator { // return .networkCongested //} break - case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .fantom, .fantom_testnet, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .fantom, .fantom_testnet, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: break } return nil @@ -306,7 +310,7 @@ class TransactionConfigurator { case .xDai: //xdai transactions are always 1 gwei in gasPrice return GasPriceConfiguration.xDaiGasPrice - case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .main, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: if let gasPrice = transaction.gasPrice, gasPrice > 0 { return min(max(gasPrice, GasPriceConfiguration.minPrice), GasPriceConfiguration.maxPrice) } else { diff --git a/AlphaWallet/Transfer/Coordinators/SendTransactionCoordinator.swift b/AlphaWallet/Transfer/Coordinators/SendTransactionCoordinator.swift index 91cf17134..afc7a4c8d 100644 --- a/AlphaWallet/Transfer/Coordinators/SendTransactionCoordinator.swift +++ b/AlphaWallet/Transfer/Coordinators/SendTransactionCoordinator.swift @@ -113,7 +113,7 @@ fileprivate extension RPCServer { } else { return rpcURL } - case .xDai, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .main, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet: + case .xDai, .kovan, .ropsten, .rinkeby, .poa, .sokol, .classic, .callisto, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .main, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum: return self.rpcURL } }