Merge pull request #3261 from vladyslav-iosdev/#3241

Add Arbitrum One RPCServer #3241
pull/3296/head
Hwee-Boon Yar 3 years ago committed by GitHub
commit a08a9d150a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      AlphaWallet.xcodeproj/project.pbxproj
  2. 21
      AlphaWallet/Assets.xcassets/arbitrum.imageset/Contents.json
  3. BIN
      AlphaWallet/Assets.xcassets/arbitrum.imageset/icons-tokens-arbitrum.pdf
  4. 10
      AlphaWallet/Core/BuyToken/Ramp/Ramp.swift
  5. 27
      AlphaWallet/Core/CoinTicker/CoinTickersFetcher.swift
  6. 23
      AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift
  7. 17
      AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift
  8. 4
      AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift
  9. 2
      AlphaWallet/Core/DomainResolver.swift
  10. 44
      AlphaWallet/Core/SwapToken/ArbitrumBridge.swift
  11. 4
      AlphaWallet/Core/SwapToken/HoneySwap/HoneySwap.swift
  12. 14
      AlphaWallet/Core/SwapToken/Oneinch/Oneinch.swift
  13. 7
      AlphaWallet/Core/SwapToken/QuickSwap/QuickSwap.swift
  14. 5
      AlphaWallet/Core/SwapToken/SwapTokenService.swift
  15. 5
      AlphaWallet/Core/SwapToken/Uniswap/Uniswap.swift
  16. 2
      AlphaWallet/Core/SwapToken/Uniswap/UniswapERC20Token.swift
  17. 41
      AlphaWallet/Core/SwapToken/xDaiBridge.swift
  18. 8
      AlphaWallet/Core/Types/NativecryptoBalanceViewModel.swift
  19. 16
      AlphaWallet/Core/Types/TokenProviderType.swift
  20. 4
      AlphaWallet/EtherClient/OpenSea.swift
  21. 4
      AlphaWallet/EtherClient/TrustClient/AlphaWalletService.swift
  22. 16
      AlphaWallet/EtherClient/TrustClient/Models/ArrayResponse.swift
  23. 2
      AlphaWallet/Gas/Models/GasNowGasPriceEstimator.swift
  24. 4
      AlphaWallet/InCoordinator.swift
  25. 2
      AlphaWallet/Localization/en.lproj/Localizable.strings
  26. 2
      AlphaWallet/Localization/es.lproj/Localizable.strings
  27. 2
      AlphaWallet/Localization/ja.lproj/Localizable.strings
  28. 2
      AlphaWallet/Localization/ko.lproj/Localizable.strings
  29. 2
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  30. 4
      AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift
  31. 1
      AlphaWallet/Settings/Coordinators/ServersCoordinator.swift
  32. 10
      AlphaWallet/Settings/Types/ConfigExplorer.swift
  33. 5
      AlphaWallet/Settings/Types/Constants.swift
  34. 51
      AlphaWallet/Settings/Types/RPCServers.swift
  35. 1
      AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift
  36. 3
      AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift
  37. 1
      AlphaWallet/Tokens/Coordinators/ENSReverseLookupCoordinator.swift
  38. 2
      AlphaWallet/Tokens/Coordinators/GetERC20BalanceCoordinator.swift
  39. 4
      AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift
  40. 1
      AlphaWallet/Tokens/Types/TokenCollection.swift
  41. 18
      AlphaWallet/Tokens/Types/TokenInstanceAction.swift
  42. 2
      AlphaWallet/Tokens/ViewControllers/Collectibles/ViewControllers/Erc1155TokenInstanceViewController.swift
  43. 2
      AlphaWallet/Tokens/ViewControllers/TokenInstanceViewController.swift
  44. 18
      AlphaWallet/Tokens/ViewControllers/TokenViewController.swift
  45. 2
      AlphaWallet/Tokens/ViewControllers/TokensCardViewController.swift
  46. 4
      AlphaWallet/Tokens/ViewModels/FungibleTokenViewCellViewModel.swift
  47. 21
      AlphaWallet/Tokens/ViewModels/TokenViewControllerViewModel.swift
  48. 43
      AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift
  49. 2
      AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift
  50. 14
      AlphaWallet/Transfer/Controllers/TransactionConfigurator.swift
  51. 2
      AlphaWallet/Transfer/Coordinators/SendTransactionCoordinator.swift

@ -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 = "<group>"; };
874015BE270DBB4800B3515F /* MimeType+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MimeType+Extension.swift"; sourceTree = "<group>"; };
8743CB4F255059780039E469 /* DomainResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainResolver.swift; sourceTree = "<group>"; };
874527D6270B3A25008DB272 /* ArbitrumBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArbitrumBridge.swift; sourceTree = "<group>"; };
874527D8270B3A45008DB272 /* xDaiBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDaiBridge.swift; sourceTree = "<group>"; };
874AF0822603405F00D613A5 /* LoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorView.swift; sourceTree = "<group>"; };
874BD2BA2669F65800E62E02 /* PopularTokensCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularTokensCollection.swift; sourceTree = "<group>"; };
874C6D8025E3FF2300AD8380 /* ConfirmationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationHeaderView.swift; sourceTree = "<group>"; };
@ -4508,6 +4512,8 @@
8731E6DD2565269400A6A7DA /* Oneinch */,
8731E6DA2565269400A6A7DA /* Uniswap */,
87B65269256FBF36000EF927 /* SwapTokenService.swift */,
874527D6270B3A25008DB272 /* ArbitrumBridge.swift */,
874527D8270B3A45008DB272 /* xDaiBridge.swift */,
);
path = SwapToken;
sourceTree = "<group>";
@ -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 */,

@ -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
}
}

@ -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
}
}

@ -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<Void>
func ticker(for addressAndPRCServer: AddressAndRPCServer) -> CoinTicker?
func fetchPrices(forTokens tokens: [TokenMappedToTicker]) -> Promise<Void>
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<AlphaWalletService>
@ -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<AlphaWalletService>, 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<Void> {
func fetchPrices(forTokens tokens: [TokenMappedToTicker]) -> Promise<Void> {
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
}

@ -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<Void> 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<Bool?> in
let resolved: [TokenBatchOperation] = value.compactMap { $0.optionalValue }.flatMap { $0 }
return tokensDatastore.batchUpdateTokenPromise(resolved).recover { _ -> Guarantee<Bool?> 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<TokenBatchOperation?> {
@ -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

@ -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<ServerDictionary<[TokenMappedToTicker]>> {
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<Void> 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() {

@ -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<Activity.AssignedToken>()
for var tokenObject in tokenObjects {
tokenObject.ticker = coinTickersFetcher.tickers[tokenObject.addressAndRPCServer]
tokenObject.ticker = coinTickersFetcher.ticker(for: tokenObject.addressAndRPCServer)
balances.insert(tokenObject)
}

@ -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
}
}

@ -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
}
}

@ -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
}
}

@ -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

@ -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
}
}

@ -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?
}

@ -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 {

@ -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
}
}

@ -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
}
}

@ -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
}
}

@ -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)
})
}
}

@ -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
}
}

@ -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 ?? "",

@ -3,5 +3,21 @@
import Foundation
struct ArrayResponse<T: Decodable>: 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
}
}
}
}

@ -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
}
}

@ -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)

@ -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";

@ -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";

@ -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";

@ -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";

@ -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";

@ -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 {

@ -37,6 +37,7 @@ class ServersCoordinator: Coordinator {
.optimistic,
.optimisticKovan,
.cronosTestnet,
.arbitrum
] + RPCServer.customServers
}

@ -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)

@ -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:///"

@ -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:

@ -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)
})
}

@ -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)
}
}
}

@ -67,7 +67,6 @@ class ENSReverseLookupCoordinator {
}
}
}.cauterize()
}
} else {
completion(.failure(AnyError(Web3Error(description: "Error extracting result from \(self.server.ensRegistrarContract).\(function.name)()"))))

@ -14,7 +14,7 @@ class GetERC20BalanceCoordinator: CallbackQueueProvider {
self.server = server
self.queue = queue
}
func getBalance(for address: AlphaWallet.Address, contract: AlphaWallet.Address) -> Promise<BigInt> {
return Promise { seal in
getBalance(for: address, contract: contract) { result in

@ -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
}

@ -127,6 +127,7 @@ extension RPCServer {
case .optimistic: return 22
case .optimisticKovan: return 23
case .cronosTestnet: return 24
case .arbitrum: return 25
}
}
}

@ -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

@ -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:

@ -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:

@ -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)

@ -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()

@ -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) ?? "-"

@ -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)
}
}
}

@ -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<AlphaWalletService>, 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<RawTransaction>.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<TransactionInstance?>] in
return try response.map(ArrayResponse<RawTransaction>.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)
}

@ -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
}

@ -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 {

@ -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
}
}

Loading…
Cancel
Save