From 5c27b4d20e71a1ce343f9eea2f38874ebe7906f8 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 14:24:16 +0300 Subject: [PATCH 01/23] master code of the send method --- .../CryptoSender/CryptoSender.swift | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 2b5364e16..da86fcd24 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -8,6 +8,8 @@ import Foundation import Boilertalk_Web3 import BigInt +import Web3ContractABI +import Web3PromiseKit typealias UDBigUInt = BigUInt @@ -135,4 +137,53 @@ struct NativeCryptoSender: CryptoSenderProtocol { // here routes to Status or Infura source try await NetworkService().fetchInfuraGasPrices(chainId: chainId) } + + + private func sendUSDT() throws { + + // Set up your Infura URL and Ethereum addresses + let infuraUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY" + let senderAddress = "0xYourSenderAddress" + let privateKey = "YourPrivateKey" + + // Set up Web3 provider + let web3 = Web3(rpcURL: "urlString") + + // Load your Ethereum address and private key + let sender = try EthereumAddress(senderAddress) + let privateKeyData = Data.fromHex(privateKey) + + // Load ERC20 contract + let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address + + let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, address: try EthereumAddress(usdtContractAddress)) + + // Send some tokens to another address (locally signing the transaction) + let myPrivateKey = try EthereumPrivateKey(hexPrivateKey: "...") + firstly { + web3.eth.getTransactionCount(address: myPrivateKey.address, block: .latest) + }.then { nonce in +// try erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction( +// nonce: nonce, +// gasPrice: EthereumQuantity(quantity: 21.gwei), +// maxFeePerGas: nil, +// maxPriorityFeePerGas: nil, +// gasLimit: 100000, +// from: myPrivateKey.address, +// value: 0, +// accessList: [:], +// transactionType: .legacy +// )!.sign(with: myPrivateKey).promise + + try (erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction(nonce: <#T##EthereumQuantity?#>, from: <#T##EthereumAddress#>, value: <#T##EthereumQuantity?#>, gas: <#T##EthereumQuantity#>, gasPrice: <#T##EthereumQuantity?#>)?.sign(with: myPrivateKey).promise)! + }.then { tx in + web3.eth.sendRawTransaction(transaction: tx) + }.done { txHash in + print(txHash) + }.catch { error in + print(error) + } + + } } + From 63f6c0674682a62c921b46c6078af2efb41a4df2 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 14:33:25 +0300 Subject: [PATCH 02/23] protocol EVMCryptoSender --- .../CryptoSender/CryptoSender.swift | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index da86fcd24..6d9b2d326 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -41,7 +41,7 @@ struct CryptoSender: CryptoSenderProtocol { } } -struct NativeCryptoSender: CryptoSenderProtocol { +struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { static let defaultSendTxGasPrice: BigUInt = 21_000 let wallet: UDWallet @@ -97,13 +97,9 @@ struct NativeCryptoSender: CryptoSenderProtocol { return gasFee } - func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { - try await fetchGasPrices(chainId: chain.id) - } - // Private methods - private func createNativeSendTransaction(crypto: CryptoSendingSpec, + internal func createNativeSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, chainId: Int) async throws -> EthereumTransaction { @@ -128,15 +124,6 @@ struct NativeCryptoSender: CryptoSenderProtocol { return transaction } - private func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMTokenAmount { - let prices: EstimatedGasPrices = try await fetchGasPrices(chainId: chainId) - return prices.getPriceForSpeed(speed) - } - - private func fetchGasPrices(chainId: Int) async throws -> EstimatedGasPrices { - // here routes to Status or Infura source - try await NetworkService().fetchInfuraGasPrices(chainId: chainId) - } private func sendUSDT() throws { @@ -187,3 +174,33 @@ struct NativeCryptoSender: CryptoSenderProtocol { } } + + +protocol EVMCryptoSender: CryptoSenderProtocol { + + + // Private methods + + func createNativeSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction + + +} + +extension EVMCryptoSender { + func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { + try await fetchGasPrices(chainId: chain.id) + } + + func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMTokenAmount { + let prices: EstimatedGasPrices = try await fetchGasPrices(chainId: chainId) + return prices.getPriceForSpeed(speed) + } + + private func fetchGasPrices(chainId: Int) async throws -> EstimatedGasPrices { + // here routes to Status or Infura source + try await NetworkService().fetchInfuraGasPrices(chainId: chainId) + } +} From 025b24fa442de5d9fecefd4f64e52badb01d20f7 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 14:45:40 +0300 Subject: [PATCH 03/23] added NonNativeCryptoSender struct --- .../CryptoSender/CryptoSender+Entities.swift | 1 + .../CryptoSender/CryptoSender.swift | 96 +++++++++++++++++-- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index a4cd883e1..9f722b6af 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -100,5 +100,6 @@ extension CryptoSender { enum SupportedToken: String { case eth = "ETH" case matic = "MATIC" + case usdt = "USDT" } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 6d9b2d326..58cf1e96e 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -59,7 +59,7 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { throw CryptoSender.Error.sendingNotSupported } - let tx = try await createNativeSendTransaction(crypto: crypto, + let tx = try await createSendTransaction(crypto: crypto, fromAddress: self.wallet.address, toAddress: toAddress, chainId: chain.id) @@ -82,7 +82,7 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { throw CryptoSender.Error.sendingNotSupported } - let transaction = try await createNativeSendTransaction(crypto: maxCrypto, + let transaction = try await createSendTransaction(crypto: maxCrypto, fromAddress: self.wallet.address, toAddress: toAddress, chainId: chain.id) @@ -99,7 +99,7 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { // Private methods - internal func createNativeSendTransaction(crypto: CryptoSendingSpec, + internal func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, chainId: Int) async throws -> EthereumTransaction { @@ -123,8 +123,90 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { } return transaction } +} + + +struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { + static let defaultSendTxGasPrice: BigUInt = 21_000 + + let wallet: UDWallet + func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { + // only native tokens supported + return false // TODO: + } + func sendCrypto(crypto: CryptoSendingSpec, + chain: ChainSpec, + toAddress: HexAddress) async throws -> String { + guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { + throw CryptoSender.Error.sendingNotSupported + } + + let tx = try await createSendTransaction(crypto: crypto, + fromAddress: self.wallet.address, + toAddress: toAddress, + chainId: chain.id) + + guard wallet.walletState != .externalLinked else { + let response = try await wallet.signViaWalletConnectTransaction(tx: tx, chainId: chain.id) + return response + } + + + let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) + return hash + } + + func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, + on chain: ChainSpec, + toAddress: HexAddress) async throws -> EVMTokenAmount { + + guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { + throw CryptoSender.Error.sendingNotSupported + } + + let transaction = try await createSendTransaction(crypto: maxCrypto, + fromAddress: self.wallet.address, + toAddress: toAddress, + chainId: chain.id) + + guard let gasPriceWei = transaction.gasPrice?.quantity else { + throw CryptoSender.Error.failedFetchGasPrice + } + let gasPrice = EVMTokenAmount(wei: gasPriceWei) + + let gas = transaction.gas?.quantity ?? Self.defaultSendTxGasPrice + let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) + return gasFee + } + + // Private methods + + internal func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction { + let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, + chainId: chainId) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + + let sender = EthereumAddress(hexString: fromAddress) + let receiver = EthereumAddress(hexString: toAddress) + + var transaction = EthereumTransaction(nonce: nonce, + gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), + gas: try EthereumQuantity(Self.defaultSendTxGasPrice), + from: sender, + to: receiver, + value: try EthereumQuantity(crypto.amount.wei) + ) + + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + transaction.gas = gasEstimate + } + return transaction + } private func sendUSDT() throws { @@ -172,21 +254,17 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { } } -} +} protocol EVMCryptoSender: CryptoSenderProtocol { - // Private methods - - func createNativeSendTransaction(crypto: CryptoSendingSpec, + func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, chainId: Int) async throws -> EthereumTransaction - - } extension EVMCryptoSender { From 87ac40d85e761f2d9787bd5f8e4d25146b22ecbb Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 15:01:40 +0300 Subject: [PATCH 04/23] implemented createTx() with master code --- .../CryptoSender/CryptoSender.swift | 69 +++++-------------- .../CryptoSender/JRPC_Client.swift | 11 ++- 2 files changed, 24 insertions(+), 56 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 58cf1e96e..686dbbade 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -194,67 +194,30 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { let sender = EthereumAddress(hexString: fromAddress) let receiver = EthereumAddress(hexString: toAddress) - var transaction = EthereumTransaction(nonce: nonce, - gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), - gas: try EthereumQuantity(Self.defaultSendTxGasPrice), - from: sender, - to: receiver, - value: try EthereumQuantity(crypto.amount.wei) - ) +// var transaction = EthereumTransaction(nonce: nonce, +// gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), +// gas: try EthereumQuantity(Self.defaultSendTxGasPrice), +// from: sender, +// to: receiver, +// value: try EthereumQuantity(crypto.amount.wei) +// ) - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { - transaction.gas = gasEstimate - } - return transaction - } - - private func sendUSDT() throws { - - // Set up your Infura URL and Ethereum addresses - let infuraUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY" - let senderAddress = "0xYourSenderAddress" - let privateKey = "YourPrivateKey" - - // Set up Web3 provider - let web3 = Web3(rpcURL: "urlString") - - // Load your Ethereum address and private key - let sender = try EthereumAddress(senderAddress) - let privateKeyData = Data.fromHex(privateKey) - // Load ERC20 contract + let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, address: try EthereumAddress(usdtContractAddress)) + + guard let transactionCreated = try erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction(nonce: <#T##EthereumQuantity?#>, from: <#T##EthereumAddress#>, value: <#T##EthereumQuantity?#>, gas: <#T##EthereumQuantity#>, gasPrice: <#T##EthereumQuantity?#>) else { + throw JRPC_Client.Error.failedFetchGas + } - // Send some tokens to another address (locally signing the transaction) - let myPrivateKey = try EthereumPrivateKey(hexPrivateKey: "...") - firstly { - web3.eth.getTransactionCount(address: myPrivateKey.address, block: .latest) - }.then { nonce in -// try erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction( -// nonce: nonce, -// gasPrice: EthereumQuantity(quantity: 21.gwei), -// maxFeePerGas: nil, -// maxPriorityFeePerGas: nil, -// gasLimit: 100000, -// from: myPrivateKey.address, -// value: 0, -// accessList: [:], -// transactionType: .legacy -// )!.sign(with: myPrivateKey).promise - - try (erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction(nonce: <#T##EthereumQuantity?#>, from: <#T##EthereumAddress#>, value: <#T##EthereumQuantity?#>, gas: <#T##EthereumQuantity#>, gasPrice: <#T##EthereumQuantity?#>)?.sign(with: myPrivateKey).promise)! - }.then { tx in - web3.eth.sendRawTransaction(transaction: tx) - }.done { txHash in - print(txHash) - }.catch { error in - print(error) + var transaction = transactionCreated + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + transaction.gas = gasEstimate } - + return transaction } - } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift index 5e4aba22d..b22fbd15b 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift @@ -79,12 +79,11 @@ struct JRPC_Client { chainIdInt: Int) async throws -> String { return try await withCheckedThrowingContinuation { continuation in - guard let urlString = NetworkService().getJRPCProviderUrl(chainId: chainIdInt)?.absoluteString else { + guard let web3 = try? getWeb3(chainIdInt: chainIdInt) else { Debugger.printFailure("Failed to get net name for chain Id: \(chainIdInt)", critical: true) continuation.resume(with: .failure(WalletConnectRequestError.failedToDetermineChainId)) return } - let web3 = Web3(rpcURL: urlString) guard let privKeyString = udWallet.getPrivateKey() else { Debugger.printFailure("No private key in \(udWallet)", critical: true) @@ -123,6 +122,12 @@ struct JRPC_Client { return } } - + } + + func getWeb3(chainIdInt: Int) throws -> Web3 { + guard let urlString = NetworkService().getJRPCProviderUrl(chainId: chainIdInt)?.absoluteString else { + throw WalletConnectRequestError.failedToDetermineChainId + } + return Web3(rpcURL: urlString) } } From 7140ddc7eecbb6fb40f88f166cca27c9663f7491 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 15:28:41 +0300 Subject: [PATCH 05/23] extracted 2 methods to protocol extension --- .../CryptoSender/CryptoSender+Entities.swift | 1 + .../CryptoSender/CryptoSender.swift | 203 +++++++++--------- 2 files changed, 103 insertions(+), 101 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index 9f722b6af..69b44332a 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -95,6 +95,7 @@ extension CryptoSender { case sendingNotSupported case failedFetchGasPrice case insufficientFunds + case invalidAddresses } enum SupportedToken: String { diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 686dbbade..465c42f91 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -14,7 +14,6 @@ import Web3PromiseKit typealias UDBigUInt = BigUInt struct CryptoSender: CryptoSenderProtocol { - let wallet: UDWallet func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { @@ -42,8 +41,7 @@ struct CryptoSender: CryptoSenderProtocol { } struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { - static let defaultSendTxGasPrice: BigUInt = 21_000 - + let defaultSendTxGasPrice: BigUInt = 21_000 let wallet: UDWallet func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { @@ -52,50 +50,50 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { (token == CryptoSender.SupportedToken.matic && chainType == .Matic) } - func sendCrypto(crypto: CryptoSendingSpec, - chain: ChainSpec, - toAddress: HexAddress) async throws -> String { - guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { - throw CryptoSender.Error.sendingNotSupported - } - - let tx = try await createSendTransaction(crypto: crypto, - fromAddress: self.wallet.address, - toAddress: toAddress, - chainId: chain.id) - - guard wallet.walletState != .externalLinked else { - let response = try await wallet.signViaWalletConnectTransaction(tx: tx, chainId: chain.id) - return response - } - - - let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) - return hash - } - - func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, - on chain: ChainSpec, - toAddress: HexAddress) async throws -> EVMTokenAmount { - - guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { - throw CryptoSender.Error.sendingNotSupported - } - - let transaction = try await createSendTransaction(crypto: maxCrypto, - fromAddress: self.wallet.address, - toAddress: toAddress, - chainId: chain.id) - - guard let gasPriceWei = transaction.gasPrice?.quantity else { - throw CryptoSender.Error.failedFetchGasPrice - } - let gasPrice = EVMTokenAmount(wei: gasPriceWei) - - let gas = transaction.gas?.quantity ?? Self.defaultSendTxGasPrice - let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) - return gasFee - } +// func sendCrypto(crypto: CryptoSendingSpec, +// chain: ChainSpec, +// toAddress: HexAddress) async throws -> String { +// guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { +// throw CryptoSender.Error.sendingNotSupported +// } +// +// let tx = try await createSendTransaction(crypto: crypto, +// fromAddress: self.wallet.address, +// toAddress: toAddress, +// chainId: chain.id) +// +// guard wallet.walletState != .externalLinked else { +// let response = try await wallet.signViaWalletConnectTransaction(tx: tx, chainId: chain.id) +// return response +// } +// +// +// let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) +// return hash +// } +// +// func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, +// on chain: ChainSpec, +// toAddress: HexAddress) async throws -> EVMTokenAmount { +// +// guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { +// throw CryptoSender.Error.sendingNotSupported +// } +// +// let transaction = try await createSendTransaction(crypto: maxCrypto, +// fromAddress: self.wallet.address, +// toAddress: toAddress, +// chainId: chain.id) +// +// guard let gasPriceWei = transaction.gasPrice?.quantity else { +// throw CryptoSender.Error.failedFetchGasPrice +// } +// let gasPrice = EVMTokenAmount(wei: gasPriceWei) +// +// let gas = transaction.gas?.quantity ?? Self.defaultSendTxGasPrice +// let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) +// return gasFee +// } // Private methods @@ -112,7 +110,7 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { var transaction = EthereumTransaction(nonce: nonce, gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), - gas: try EthereumQuantity(Self.defaultSendTxGasPrice), + gas: try EthereumQuantity(defaultSendTxGasPrice), from: sender, to: receiver, value: try EthereumQuantity(crypto.amount.wei) @@ -127,14 +125,67 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { - static let defaultSendTxGasPrice: BigUInt = 21_000 + let defaultSendTxGasPrice: BigUInt = 100_000 let wallet: UDWallet func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { // only native tokens supported - return false // TODO: + return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: + } + + + // Private methods + + internal func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction { + let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, + chainId: chainId) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + + guard let sender = EthereumAddress(hexString: fromAddress), + let receiver = EthereumAddress(hexString: toAddress) else { + throw CryptoSender.Error.invalidAddresses + } + // Load ERC20 contract + let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) + let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address + + let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, address: try EthereumAddress(usdtContractAddress)) + + guard let transactionCreated = erc20Contract + .transfer(to: receiver, value: crypto.amount.wei) + .createTransaction(nonce: nonce, + from: sender, + value: 0, // zero native tokens transferred + gas: 100000, // TODO: + gasPrice: try EthereumQuantity(speedBasedGasPrice.wei)) else { + throw JRPC_Client.Error.failedFetchGas + } + + var transaction = transactionCreated + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + transaction.gas = gasEstimate + } + return transaction } +} + + +protocol EVMCryptoSender: CryptoSenderProtocol { + + var wallet: UDWallet { get } + var defaultSendTxGasPrice: BigUInt { get } + + func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction +} + +extension EVMCryptoSender { func sendCrypto(crypto: CryptoSendingSpec, chain: ChainSpec, @@ -176,61 +227,11 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { } let gasPrice = EVMTokenAmount(wei: gasPriceWei) - let gas = transaction.gas?.quantity ?? Self.defaultSendTxGasPrice + let gas = transaction.gas?.quantity ?? defaultSendTxGasPrice let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) return gasFee } - - // Private methods - - internal func createSendTransaction(crypto: CryptoSendingSpec, - fromAddress: HexAddress, - toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction { - let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, - chainId: chainId) - let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) - - let sender = EthereumAddress(hexString: fromAddress) - let receiver = EthereumAddress(hexString: toAddress) - -// var transaction = EthereumTransaction(nonce: nonce, -// gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), -// gas: try EthereumQuantity(Self.defaultSendTxGasPrice), -// from: sender, -// to: receiver, -// value: try EthereumQuantity(crypto.amount.wei) -// ) - - // Load ERC20 contract - let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) - let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address - - let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, address: try EthereumAddress(usdtContractAddress)) - - guard let transactionCreated = try erc20Contract.transfer(to: EthereumAddress(hex: "0x3edB3b95DDe29580FFC04b46A68a31dD46106a4a", eip55: true), value: 100000).createTransaction(nonce: <#T##EthereumQuantity?#>, from: <#T##EthereumAddress#>, value: <#T##EthereumQuantity?#>, gas: <#T##EthereumQuantity#>, gasPrice: <#T##EthereumQuantity?#>) else { - throw JRPC_Client.Error.failedFetchGas - } - - var transaction = transactionCreated - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { - transaction.gas = gasEstimate - } - return transaction - } -} - -protocol EVMCryptoSender: CryptoSenderProtocol { - - // Private methods - func createSendTransaction(crypto: CryptoSendingSpec, - fromAddress: HexAddress, - toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction -} - -extension EVMCryptoSender { func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { try await fetchGasPrices(chainId: chain.id) } From 68d84d2e61bcf154c73af96f1b2cf1e8c5c74798 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 15:42:04 +0300 Subject: [PATCH 06/23] cleaning --- .../CryptoSender/CryptoSender.swift | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 465c42f91..829e934b6 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -50,53 +50,6 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { (token == CryptoSender.SupportedToken.matic && chainType == .Matic) } -// func sendCrypto(crypto: CryptoSendingSpec, -// chain: ChainSpec, -// toAddress: HexAddress) async throws -> String { -// guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { -// throw CryptoSender.Error.sendingNotSupported -// } -// -// let tx = try await createSendTransaction(crypto: crypto, -// fromAddress: self.wallet.address, -// toAddress: toAddress, -// chainId: chain.id) -// -// guard wallet.walletState != .externalLinked else { -// let response = try await wallet.signViaWalletConnectTransaction(tx: tx, chainId: chain.id) -// return response -// } -// -// -// let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) -// return hash -// } -// -// func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, -// on chain: ChainSpec, -// toAddress: HexAddress) async throws -> EVMTokenAmount { -// -// guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { -// throw CryptoSender.Error.sendingNotSupported -// } -// -// let transaction = try await createSendTransaction(crypto: maxCrypto, -// fromAddress: self.wallet.address, -// toAddress: toAddress, -// chainId: chain.id) -// -// guard let gasPriceWei = transaction.gasPrice?.quantity else { -// throw CryptoSender.Error.failedFetchGasPrice -// } -// let gasPrice = EVMTokenAmount(wei: gasPriceWei) -// -// let gas = transaction.gas?.quantity ?? Self.defaultSendTxGasPrice -// let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) -// return gasFee -// } - - // Private methods - internal func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, @@ -134,9 +87,6 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: } - - // Private methods - internal func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, From d9ad7b8f20cc47dcfa4bd3d484b9482234015879 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 18:36:51 +0300 Subject: [PATCH 07/23] routing logic --- .../SendCryptoAsset/CryptoSender/CryptoSender.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 829e934b6..863e480bc 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -18,13 +18,20 @@ struct CryptoSender: CryptoSenderProtocol { func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { // only native tokens supported for Ethereum and Polygon - return NativeCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) + return NativeCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) || NonNativeCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) } func sendCrypto(crypto: CryptoSendingSpec, chain: ChainSpec, toAddress: HexAddress) async throws -> String { let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) - return try await cryptoSender.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) - + if cryptoSender.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { + return try await cryptoSender.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) + } + + let cryptoSender2: CryptoSenderProtocol = NonNativeCryptoSender(wallet: wallet) + if cryptoSender2.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { + return try await cryptoSender2.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) + } + throw CryptoSender.Error.sendingNotSupported } func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMTokenAmount { From 13e007fe0be144cece59f4d645f4800bc15e3469 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 8 Apr 2024 19:16:25 +0300 Subject: [PATCH 08/23] eip55 compliance --- .../SendCryptoAsset/CryptoSender/CryptoSender.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 863e480bc..cde67d160 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -102,15 +102,19 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { chainId: chainId) let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) - guard let sender = EthereumAddress(hexString: fromAddress), - let receiver = EthereumAddress(hexString: toAddress) else { + guard let sender = try? EthereumAddress(hex: fromAddress, eip55: false), + let receiver = try? EthereumAddress(hex: toAddress, eip55: false) else { throw CryptoSender.Error.invalidAddresses } // Load ERC20 contract let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address + guard let contractAddress = try? EthereumAddress(hex: usdtContractAddress, eip55: false) else { + throw CryptoSender.Error.invalidAddresses + } - let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, address: try EthereumAddress(usdtContractAddress)) + let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, + address: contractAddress) guard let transactionCreated = erc20Contract .transfer(to: receiver, value: crypto.amount.wei) From 21a7e320650b71a7949d2e01edc6a47941945300 Mon Sep 17 00:00:00 2001 From: rommex Date: Tue, 9 Apr 2024 12:57:21 +0300 Subject: [PATCH 09/23] computeGas() updated --- .../SendCryptoAsset/CryptoSender/CryptoSender.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index cde67d160..b562f97cb 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -36,9 +36,15 @@ struct CryptoSender: CryptoSenderProtocol { func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMTokenAmount { let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) - return try await cryptoSender.computeGasFeeFrom(maxCrypto: maxCrypto, - on: chain, - toAddress: toAddress) + if cryptoSender.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { + return try await cryptoSender.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) + } + + let cryptoSender2: CryptoSenderProtocol = NonNativeCryptoSender(wallet: wallet) + if cryptoSender2.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { + return try await cryptoSender2.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) + } + throw CryptoSender.Error.sendingNotSupported } func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { From 72f6eaed835dcf1356a2e8534925ca621d7d7b3d Mon Sep 17 00:00:00 2001 From: rommex Date: Tue, 9 Apr 2024 13:19:02 +0300 Subject: [PATCH 10/23] error handling --- .../CryptoSender/CryptoSender+Entities.swift | 1 + .../CryptoSender/CryptoSender.swift | 2 +- .../Services/Networking/NetworkService.swift | 16 +++++++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index 69b44332a..49d888282 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -94,6 +94,7 @@ extension CryptoSender { enum Error: Swift.Error { case sendingNotSupported case failedFetchGasPrice + case failedCreateSendTransaction case insufficientFunds case invalidAddresses } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index b562f97cb..16088ab6e 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -129,7 +129,7 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { value: 0, // zero native tokens transferred gas: 100000, // TODO: gasPrice: try EthereumQuantity(speedBasedGasPrice.wei)) else { - throw JRPC_Client.Error.failedFetchGas + throw CryptoSender.Error.failedCreateSendTransaction } var transaction = transactionCreated diff --git a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift index c0b89c194..669112823 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift @@ -320,6 +320,7 @@ extension NetworkService { case failedGetStatus case failedParseStatusPrices case failedParseInfuraPrices + case failedEncodeTxParameters case unknownChain init(message: String) { @@ -362,10 +363,13 @@ extension NetworkService { func getGasEstimation(tx: EthereumTransaction, chainId: Int) async throws -> String { + guard let params = tx.parameters else { + throw JRPCError.failedEncodeTxParameters + } - try await getJRPCRequest(chainId: chainId, + return try await getJRPCRequest(chainId: chainId, requestInfo: JRPCRequestInfo(name: "eth_estimateGas", - paramsBuilder: { "[\(tx.parameters), \"latest\"]"} )) + paramsBuilder: { "[\(params), \"latest\"]"} )) } func getGasPrice(chainId: Int) async throws -> String { @@ -497,7 +501,7 @@ extension NetworkService { } extension EthereumTransaction { - var parameters: String { + var parameters: String? { var object: [String: String] = [:] if let from = self.from { object["from"] = from.hex() @@ -513,8 +517,10 @@ extension EthereumTransaction { } object["data"] = data.hex() - let data = (try? JSONEncoder().encode(object)) ?? Data() - return String(data: data, encoding: .utf8) ?? "" + guard let data = try? JSONEncoder().encode(object) else { + return nil + } + return String(data: data, encoding: .utf8) } } From f4610ee9a85f265ed36a2741638ed0b404bcc063 Mon Sep 17 00:00:00 2001 From: rommex Date: Tue, 9 Apr 2024 13:54:06 +0300 Subject: [PATCH 11/23] generic error handling --- .../Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift index b22fbd15b..9abd7d9d0 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/JRPC_Client.swift @@ -14,6 +14,7 @@ struct JRPC_Client { enum Error: Swift.Error { case failedFetchGas + case lowAllowance case failedFetchGasLimit } @@ -62,9 +63,12 @@ struct JRPC_Client { } catch { if let jrpcError = error as? NetworkService.JRPCError { switch jrpcError { + case .genericError(let message): + Debugger.printFailure("Failed to fetch gas Estimate, message: \(message)", critical: false) + throw JRPC_Client.Error.failedFetchGas case .gasRequiredExceedsAllowance: Debugger.printFailure("Failed to fetch gas Estimate because of Low Allowance Error", critical: false) - throw WalletConnectRequestError.lowAllowance + throw JRPC_Client.Error.lowAllowance default: throw WalletConnectRequestError.failedFetchGas } } else { From 659105f73d337e1341c0deb9c9523b34b38fd33f Mon Sep 17 00:00:00 2001 From: rommex Date: Wed, 10 Apr 2024 13:10:52 +0300 Subject: [PATCH 12/23] introduced ERC20TokenAmount --- .../SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index 49d888282..b549c5518 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -43,6 +43,10 @@ struct EVMTokenAmount { } } +protocol ERC20TokenAmount { + var decimals: UInt8 { get } +} + struct EstimatedGasPrices { let normal: EVMTokenAmount let fast: EVMTokenAmount From 99e2908f5697b37ca47cabff5645816a93f657af Mon Sep 17 00:00:00 2001 From: rommex Date: Wed, 10 Apr 2024 13:15:39 +0300 Subject: [PATCH 13/23] Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2ec665a7..fa65a9951 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "57712317d52b57378eb742d2fbaf64669df05a363013ace30768d4859c34d342", "pins" : [ { "identity" : "amplitude-ios", @@ -7,7 +6,7 @@ "location" : "https://github.com/amplitude/Amplitude-iOS", "state" : { "branch" : "main", - "revision" : "dca2c744336e651ee285011282b86accc2ce3704" + "revision" : "55188b6a03a1ed6883521dd639eb986f7a2ddf66" } }, { @@ -60,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "db51c407d3be4a051484a141bf0bff36c43d3b1e", - "version" : "1.8.0" + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" } }, { @@ -185,8 +184,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", - "version" : "1.0.6" + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" } }, { @@ -203,8 +202,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types", "state" : { - "revision" : "1827dc94bdab2eb5f2fc804e9b0cb43574282566", - "version" : "1.0.2" + "revision" : "12358d55a3824bd5fed310b999ea8cf83a9a1a65", + "version" : "1.0.3" } }, { @@ -212,8 +211,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", - "version" : "1.5.3" + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" } }, { @@ -221,8 +220,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", - "version" : "2.62.0" + "revision" : "fc63f0cf4e55a4597407a9fc95b16a2bc44b4982", + "version" : "2.64.0" } }, { @@ -230,8 +229,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "798c962495593a23fdea0c0c63fd55571d8dff51", - "version" : "1.20.0" + "revision" : "a3b640d7dc567225db7c94386a6e71aded1bfa63", + "version" : "1.22.0" } }, { @@ -239,8 +238,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "3bd9004b9d685ed6b629760fc84903e48efec806", - "version" : "1.29.0" + "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", + "version" : "1.30.0" } }, { @@ -248,8 +247,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9", - "version" : "2.25.0" + "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version" : "2.26.0" } }, { @@ -257,8 +256,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "ebf8b9c365a6ce043bf6e6326a04b15589bd285e", - "version" : "1.20.0" + "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", + "version" : "1.20.1" } }, { @@ -266,8 +265,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", - "version" : "1.25.2" + "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", + "version" : "1.26.0" } }, { @@ -279,6 +278,15 @@ "version" : "1.0.3" } }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" + } + }, { "identity" : "swiftimagereadwrite", "kind" : "remoteSourceControl", @@ -329,8 +337,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/vapor/websocket-kit.git", "state" : { - "revision" : "53fe0639a98903858d0196b699720decb42aee7b", - "version" : "2.14.0" + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" } }, { @@ -351,5 +359,5 @@ } } ], - "version" : 3 + "version" : 2 } From b5262986649e1a1f1ec02e2707a117062603bd36 Mon Sep 17 00:00:00 2001 From: rommex Date: Sat, 20 Apr 2024 22:25:39 +0300 Subject: [PATCH 14/23] renaming --- .../domains-manager-ios/Entities/Wallets/UDWallet+Signing.swift | 2 +- .../Home/SendCryptoAsset/CryptoSender/CryptoSender.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Entities/Wallets/UDWallet+Signing.swift b/unstoppable-ios-app/domains-manager-ios/Entities/Wallets/UDWallet+Signing.swift index bcd197cbf..c8f4dfc89 100644 --- a/unstoppable-ios-app/domains-manager-ios/Entities/Wallets/UDWallet+Signing.swift +++ b/unstoppable-ios-app/domains-manager-ios/Entities/Wallets/UDWallet+Signing.swift @@ -172,7 +172,7 @@ extension UDWallet { return try appContext.walletConnectServiceV2.handle(response: response) } - func signViaWalletConnectTransaction(tx: EthereumTransaction, chainId: Int) async throws -> String { + func sendViaWalletConnectTransaction(tx: EthereumTransaction, chainId: Int) async throws -> String { let wc2Sessions = try getWC2Session() let response = try await appContext.walletConnectServiceV2.sendSignTx(sessions: wc2Sessions, chainId: chainId, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 16088ab6e..34a6dbcab 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -167,7 +167,7 @@ extension EVMCryptoSender { chainId: chain.id) guard wallet.walletState != .externalLinked else { - let response = try await wallet.signViaWalletConnectTransaction(tx: tx, chainId: chain.id) + let response = try await wallet.sendViaWalletConnectTransaction(tx: tx, chainId: chain.id) return response } From fc4d8dbd331e9b8677f37452b582a9c08dca6ccf Mon Sep 17 00:00:00 2001 From: rommex Date: Sat, 20 Apr 2024 23:24:41 +0300 Subject: [PATCH 15/23] Introduced struct USDT --- .../CryptoSender/CryptoSender+Entities.swift | 40 +++++++++++++++---- .../CryptoSender/CryptoSender.swift | 10 ++--- .../CryptoSender/CryptoSenderProtocol.swift | 2 +- .../SendCryptoAssetViewModel.swift | 2 +- .../Services/Networking/NetworkService.swift | 12 +++--- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index b549c5518..e261b557b 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -10,7 +10,7 @@ import Foundation // Unified container for the token amount. // Init with units, gwei's or wei's // Read in units, gwei's or wei's -struct EVMTokenAmount { +struct EVMCoinAmount: OnChainCountable { static let Billion = 1_000_000_000.0 private let gweiTotal: Double @@ -41,18 +41,44 @@ struct EVMTokenAmount { var wei: UDBigUInt { // can only be integer and may be very big UDBigUInt(gweiTotal * Self.Billion) } + + func getOnChainCountable() -> UDBigUInt { + self.wei + } } protocol ERC20TokenAmount { var decimals: UInt8 { get } } +struct USDT: ERC20TokenAmount, OnChainCountable { + var elementaryUnits: UDBigUInt + + init(units: Double) { + self.elementaryUnits = UDBigUInt(units * pow(10, Double(decimals))) + } + + func getOnChainCountable() -> UDBigUInt { + elementaryUnits + } + + let decimals: UInt8 = 6 + + var units: Double { + Double(elementaryUnits) / pow(10, Double(decimals)) + } +} + +protocol OnChainCountable { + func getOnChainCountable() -> UDBigUInt +} + struct EstimatedGasPrices { - let normal: EVMTokenAmount - let fast: EVMTokenAmount - let urgent: EVMTokenAmount + let normal: EVMCoinAmount + let fast: EVMCoinAmount + let urgent: EVMCoinAmount - func getPriceForSpeed(_ txSpeed: CryptoSendingSpec.TxSpeed) -> EVMTokenAmount { + func getPriceForSpeed(_ txSpeed: CryptoSendingSpec.TxSpeed) -> EVMCoinAmount { switch txSpeed { case .normal: return normal @@ -84,10 +110,10 @@ struct CryptoSendingSpec { } let token: CryptoSender.SupportedToken - let amount: EVMTokenAmount + let amount: EVMCoinAmount let speed: TxSpeed - init(token: CryptoSender.SupportedToken, amount: EVMTokenAmount, speed: TxSpeed = .normal) { + init(token: CryptoSender.SupportedToken, amount: EVMCoinAmount, speed: TxSpeed = .normal) { self.token = token self.amount = amount self.speed = speed diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 34a6dbcab..9775581c8 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -34,7 +34,7 @@ struct CryptoSender: CryptoSenderProtocol { throw CryptoSender.Error.sendingNotSupported } - func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMTokenAmount { + func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMCoinAmount { let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) if cryptoSender.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { return try await cryptoSender.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) @@ -178,7 +178,7 @@ extension EVMCryptoSender { func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, - toAddress: HexAddress) async throws -> EVMTokenAmount { + toAddress: HexAddress) async throws -> EVMCoinAmount { guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { throw CryptoSender.Error.sendingNotSupported @@ -192,10 +192,10 @@ extension EVMCryptoSender { guard let gasPriceWei = transaction.gasPrice?.quantity else { throw CryptoSender.Error.failedFetchGasPrice } - let gasPrice = EVMTokenAmount(wei: gasPriceWei) + let gasPrice = EVMCoinAmount(wei: gasPriceWei) let gas = transaction.gas?.quantity ?? defaultSendTxGasPrice - let gasFee = EVMTokenAmount(gwei: gasPrice.gwei * Double(gas)) + let gasFee = EVMCoinAmount(gwei: gasPrice.gwei * Double(gas)) return gasFee } @@ -203,7 +203,7 @@ extension EVMCryptoSender { try await fetchGasPrices(chainId: chain.id) } - func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMTokenAmount { + func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMCoinAmount { let prices: EstimatedGasPrices = try await fetchGasPrices(chainId: chainId) return prices.getPriceForSpeed(speed) } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift index 5b7b10bdd..4bc55d423 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift @@ -36,7 +36,7 @@ protocol CryptoSenderProtocol { /// - Returns: Amount of crypto that must be deducted from maxCrypto as the gas fee in the future tx, in token units func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, - toAddress: HexAddress) async throws -> EVMTokenAmount + toAddress: HexAddress) async throws -> EVMCoinAmount func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift index 4a5b605fd..bc1e615a6 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift @@ -143,7 +143,7 @@ final class SendCryptoAssetViewModel: ObservableObject { tokenAmount: Double, txSpeed: SendCryptoAsset.TransactionSpeed) throws -> CryptoSendingSpec { let token = try getSupportedTokenFor(balanceToken: token) - let amount = EVMTokenAmount(units: tokenAmount) + let amount = EVMCoinAmount(units: tokenAmount) let speed = getSpecTransactionSpeedFor(txSpeed: txSpeed) return CryptoSendingSpec(token: token, diff --git a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift index 487bdf5b1..b0c631b81 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift @@ -417,9 +417,9 @@ extension NetworkService { } assert(priceDict.count == Self.InfuraSpeedCase.allCases.count) // always true after forEach - return EstimatedGasPrices(normal: EVMTokenAmount(gwei: priceDict[InfuraSpeedCase.low]!), - fast: EVMTokenAmount(gwei: priceDict[InfuraSpeedCase.medium]!), - urgent: EVMTokenAmount(gwei: priceDict[InfuraSpeedCase.high]!)) + return EstimatedGasPrices(normal: EVMCoinAmount(gwei: priceDict[InfuraSpeedCase.low]!), + fast: EVMCoinAmount(gwei: priceDict[InfuraSpeedCase.medium]!), + urgent: EVMCoinAmount(gwei: priceDict[InfuraSpeedCase.high]!)) } func getStatusGasPrices(chainId: Int) async throws -> EstimatedGasPrices { @@ -430,9 +430,9 @@ extension NetworkService { let urgent = prices["fastest"] else { throw CryptoSender.Error.failedFetchGasPrice } - return EstimatedGasPrices(normal: EVMTokenAmount(gwei: normal), - fast: EVMTokenAmount(gwei: fast), - urgent: EVMTokenAmount(gwei: urgent)) + return EstimatedGasPrices(normal: EVMCoinAmount(gwei: normal), + fast: EVMCoinAmount(gwei: fast), + urgent: EVMCoinAmount(gwei: urgent)) } private func getStatusGasPrices(chainId: Int) async throws -> [String: Int] { From a0053354f7a2b500873544ce13ab32f7cfd5b0d6 Mon Sep 17 00:00:00 2001 From: rommex Date: Sat, 20 Apr 2024 23:46:03 +0300 Subject: [PATCH 16/23] using protocol-compliant type --- .../SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift | 4 ++-- .../Home/SendCryptoAsset/CryptoSender/CryptoSender.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index e261b557b..a67a72861 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -110,10 +110,10 @@ struct CryptoSendingSpec { } let token: CryptoSender.SupportedToken - let amount: EVMCoinAmount + let amount: OnChainCountable let speed: TxSpeed - init(token: CryptoSender.SupportedToken, amount: EVMCoinAmount, speed: TxSpeed = .normal) { + init(token: CryptoSender.SupportedToken, amount: OnChainCountable, speed: TxSpeed = .normal) { self.token = token self.amount = amount self.speed = speed diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 9775581c8..bd204106e 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -79,7 +79,7 @@ struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { gas: try EthereumQuantity(defaultSendTxGasPrice), from: sender, to: receiver, - value: try EthereumQuantity(crypto.amount.wei) + value: try EthereumQuantity(crypto.amount.getOnChainCountable()) ) if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { @@ -123,7 +123,7 @@ struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { address: contractAddress) guard let transactionCreated = erc20Contract - .transfer(to: receiver, value: crypto.amount.wei) + .transfer(to: receiver, value: crypto.amount.getOnChainCountable()) .createTransaction(nonce: nonce, from: sender, value: 0, // zero native tokens transferred From ed0e0211972486efb7d6f20899d8c2038e09d02e Mon Sep 17 00:00:00 2001 From: rommex Date: Sun, 21 Apr 2024 13:50:11 +0300 Subject: [PATCH 17/23] refactoring --- .../project.pbxproj | 8 + .../CryptoSender/CryptoSender.swift | 179 +----------------- .../CryptoSender/EVMCryptoSender.swift | 172 +++++++++++++++++ 3 files changed, 186 insertions(+), 173 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index 2d3a45cca..1685eee0a 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -21,6 +21,9 @@ 2917EDDD2A6F25AB004EAB31 /* Web3ContractABI in Frameworks */ = {isa = PBXBuildFile; productRef = 2917EDDC2A6F25AB004EAB31 /* Web3ContractABI */; }; 2917EDDF2A6F25AB004EAB31 /* Web3PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2917EDDE2A6F25AB004EAB31 /* Web3PromiseKit */; }; 291A9C6A29E444CF00527FF1 /* WC_v1+v2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291A9C6929E444CF00527FF1 /* WC_v1+v2.swift */; }; + 291AD2452BD5265E006B8096 /* EVMCryptoSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291AD2442BD5265E006B8096 /* EVMCryptoSender.swift */; }; + 291AD2462BD5265E006B8096 /* EVMCryptoSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291AD2442BD5265E006B8096 /* EVMCryptoSender.swift */; }; + 291AD2472BD5265E006B8096 /* EVMCryptoSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291AD2442BD5265E006B8096 /* EVMCryptoSender.swift */; }; 295506F02954D14400EDA42D /* WCConnectedAppsStorageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295506EF2954D14400EDA42D /* WCConnectedAppsStorageV2.swift */; }; 2959AC4729A61916006387DD /* UDWallet+SigningTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2959AC4629A61916006387DD /* UDWallet+SigningTransaction.swift */; }; 296BB61F2A309DD90068EEEC /* Encrypting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296BB61E2A309DD90068EEEC /* Encrypting.swift */; }; @@ -2552,6 +2555,7 @@ 290A60412950A89900882109 /* WalletConnectServiceV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectServiceV2.swift; sourceTree = ""; }; 29150FBA2975DA4D00169A1A /* PushSubscriberInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSubscriberInfo.swift; sourceTree = ""; }; 291A9C6929E444CF00527FF1 /* WC_v1+v2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WC_v1+v2.swift"; sourceTree = ""; }; + 291AD2442BD5265E006B8096 /* EVMCryptoSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EVMCryptoSender.swift; sourceTree = ""; }; 295506EF2954D14400EDA42D /* WCConnectedAppsStorageV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WCConnectedAppsStorageV2.swift; sourceTree = ""; }; 2959AC4629A61916006387DD /* UDWallet+SigningTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UDWallet+SigningTransaction.swift"; sourceTree = ""; }; 296BB61E2A309DD90068EEEC /* Encrypting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encrypting.swift; sourceTree = ""; }; @@ -7314,6 +7318,7 @@ C6B45DAF2BBA97B600B44C33 /* CryptoSender+Entities.swift */, 29AA6AA72BAC389D00D24FB5 /* CryptoSender.swift */, 29018C8C2BACB7BC0004545D /* JRPC_Client.swift */, + 291AD2442BD5265E006B8096 /* EVMCryptoSender.swift */, ); path = CryptoSender; sourceTree = ""; @@ -9048,6 +9053,7 @@ C635193C28D03F8F00FC6AF8 /* ChooseReverseResolutionDomainViewController.swift in Sources */, C6386843285C2D4E000F98C4 /* UpgradeToPolygonTutorial.swift in Sources */, C628E37227FDE1D60044E408 /* UDSubtitleLabel.swift in Sources */, + 291AD2452BD5265E006B8096 /* EVMCryptoSender.swift in Sources */, C671E3E42902817000A2B3A0 /* DomainProfileGeneralInfoCell.swift in Sources */, C6E041A42951A8C20080F8E3 /* ConnectedAppImageView.swift in Sources */, C666E09929CB3F960003DECB /* FirebaseDomain.swift in Sources */, @@ -9916,6 +9922,7 @@ C66FFD052B01E2EE00988A6F /* CoreDataMessagingStorageServiceTests.swift in Sources */, C617CFA82B9EDA2F00663516 /* TestableWalletNFTsService.swift in Sources */, C60610E829A7469A005DC0D5 /* WCRequestsHandlingServiceTests.swift in Sources */, + 291AD2462BD5265E006B8096 /* EVMCryptoSender.swift in Sources */, C6DF9875290F83020098733A /* UnsConfigManagerTests.swift in Sources */, C6DF986B290F83020098733A /* WalletAutonamingTests.swift in Sources */, C6DF9871290F83020098733A /* ResolutionInitTests.swift in Sources */, @@ -10306,6 +10313,7 @@ C6D646A82B1ED15A00D724AC /* PublicProfileCryptoListView.swift in Sources */, C630E4AD2B7F4B8D008F3269 /* FlippedUpsideDownModifier.swift in Sources */, C6D646E22B1ED49D00D724AC /* TableViewSelectionCell.swift in Sources */, + 291AD2472BD5265E006B8096 /* EVMCryptoSender.swift in Sources */, C6FBCAA32B91C6AC00BA39DF /* HomeExploreRecentProfilesSectionView.swift in Sources */, C6C8F8262B217CDD00A9834D /* UDWallet+RecoveryType.swift in Sources */, C6D647182B1ED83800D724AC /* CollectionTextFooterReusableView.swift in Sources */, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index bd204106e..4678db3ad 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -6,28 +6,22 @@ // import Foundation -import Boilertalk_Web3 -import BigInt -import Web3ContractABI -import Web3PromiseKit - -typealias UDBigUInt = BigUInt struct CryptoSender: CryptoSenderProtocol { let wallet: UDWallet func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { // only native tokens supported for Ethereum and Polygon - return NativeCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) || NonNativeCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) + return NativeCoinCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) || TokenCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) } func sendCrypto(crypto: CryptoSendingSpec, chain: ChainSpec, toAddress: HexAddress) async throws -> String { - let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) + let cryptoSender: CryptoSenderProtocol = NativeCoinCryptoSender(wallet: wallet) if cryptoSender.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { return try await cryptoSender.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) } - let cryptoSender2: CryptoSenderProtocol = NonNativeCryptoSender(wallet: wallet) + let cryptoSender2: CryptoSenderProtocol = TokenCryptoSender(wallet: wallet) if cryptoSender2.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { return try await cryptoSender2.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) } @@ -35,12 +29,12 @@ struct CryptoSender: CryptoSenderProtocol { } func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMCoinAmount { - let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) + let cryptoSender: CryptoSenderProtocol = NativeCoinCryptoSender(wallet: wallet) if cryptoSender.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { return try await cryptoSender.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) } - let cryptoSender2: CryptoSenderProtocol = NonNativeCryptoSender(wallet: wallet) + let cryptoSender2: CryptoSenderProtocol = TokenCryptoSender(wallet: wallet) if cryptoSender2.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { return try await cryptoSender2.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) } @@ -48,168 +42,7 @@ struct CryptoSender: CryptoSenderProtocol { } func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { - let cryptoSender: CryptoSenderProtocol = NativeCryptoSender(wallet: wallet) + let cryptoSender: CryptoSenderProtocol = NativeCoinCryptoSender(wallet: wallet) return try await cryptoSender.fetchGasPrices(on: chain) } } - -struct NativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { - let defaultSendTxGasPrice: BigUInt = 21_000 - let wallet: UDWallet - - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { - // only native tokens supported - return (token == CryptoSender.SupportedToken.eth && chainType == .Ethereum) || - (token == CryptoSender.SupportedToken.matic && chainType == .Matic) - } - - internal func createSendTransaction(crypto: CryptoSendingSpec, - fromAddress: HexAddress, - toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction { - let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, - chainId: chainId) - let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) - - let sender = EthereumAddress(hexString: fromAddress) - let receiver = EthereumAddress(hexString: toAddress) - - var transaction = EthereumTransaction(nonce: nonce, - gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), - gas: try EthereumQuantity(defaultSendTxGasPrice), - from: sender, - to: receiver, - value: try EthereumQuantity(crypto.amount.getOnChainCountable()) - ) - - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { - transaction.gas = gasEstimate - } - return transaction - } -} - - -struct NonNativeCryptoSender: CryptoSenderProtocol, EVMCryptoSender { - let defaultSendTxGasPrice: BigUInt = 100_000 - - let wallet: UDWallet - - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { - // only native tokens supported - return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: - } - - internal func createSendTransaction(crypto: CryptoSendingSpec, - fromAddress: HexAddress, - toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction { - let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, - chainId: chainId) - let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) - - guard let sender = try? EthereumAddress(hex: fromAddress, eip55: false), - let receiver = try? EthereumAddress(hex: toAddress, eip55: false) else { - throw CryptoSender.Error.invalidAddresses - } - // Load ERC20 contract - let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) - let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address - guard let contractAddress = try? EthereumAddress(hex: usdtContractAddress, eip55: false) else { - throw CryptoSender.Error.invalidAddresses - } - - let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, - address: contractAddress) - - guard let transactionCreated = erc20Contract - .transfer(to: receiver, value: crypto.amount.getOnChainCountable()) - .createTransaction(nonce: nonce, - from: sender, - value: 0, // zero native tokens transferred - gas: 100000, // TODO: - gasPrice: try EthereumQuantity(speedBasedGasPrice.wei)) else { - throw CryptoSender.Error.failedCreateSendTransaction - } - - var transaction = transactionCreated - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { - transaction.gas = gasEstimate - } - return transaction - } -} - - -protocol EVMCryptoSender: CryptoSenderProtocol { - - var wallet: UDWallet { get } - var defaultSendTxGasPrice: BigUInt { get } - - func createSendTransaction(crypto: CryptoSendingSpec, - fromAddress: HexAddress, - toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction -} - -extension EVMCryptoSender { - - func sendCrypto(crypto: CryptoSendingSpec, - chain: ChainSpec, - toAddress: HexAddress) async throws -> String { - guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { - throw CryptoSender.Error.sendingNotSupported - } - - let tx = try await createSendTransaction(crypto: crypto, - fromAddress: self.wallet.address, - toAddress: toAddress, - chainId: chain.id) - - guard wallet.walletState != .externalLinked else { - let response = try await wallet.sendViaWalletConnectTransaction(tx: tx, chainId: chain.id) - return response - } - - - let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) - return hash - } - - func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, - on chain: ChainSpec, - toAddress: HexAddress) async throws -> EVMCoinAmount { - - guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { - throw CryptoSender.Error.sendingNotSupported - } - - let transaction = try await createSendTransaction(crypto: maxCrypto, - fromAddress: self.wallet.address, - toAddress: toAddress, - chainId: chain.id) - - guard let gasPriceWei = transaction.gasPrice?.quantity else { - throw CryptoSender.Error.failedFetchGasPrice - } - let gasPrice = EVMCoinAmount(wei: gasPriceWei) - - let gas = transaction.gas?.quantity ?? defaultSendTxGasPrice - let gasFee = EVMCoinAmount(gwei: gasPrice.gwei * Double(gas)) - return gasFee - } - - func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { - try await fetchGasPrices(chainId: chain.id) - } - - func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMCoinAmount { - let prices: EstimatedGasPrices = try await fetchGasPrices(chainId: chainId) - return prices.getPriceForSpeed(speed) - } - - private func fetchGasPrices(chainId: Int) async throws -> EstimatedGasPrices { - // here routes to Status or Infura source - try await NetworkService().fetchInfuraGasPrices(chainId: chainId) - } -} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift new file mode 100644 index 000000000..598ad289b --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift @@ -0,0 +1,172 @@ +// +// EVMCryptoSender.swift +// domains-manager-ios +// +// Created by Roman Medvid on 21.04.2024. +// + +import Foundation +import Boilertalk_Web3 +import BigInt +import Web3ContractABI +import Web3PromiseKit + +typealias UDBigUInt = BigUInt + +protocol EVMCryptoSender: CryptoSenderProtocol { + var wallet: UDWallet { get } + var defaultSendTxGasPrice: BigUInt { get } + + func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction +} + +extension EVMCryptoSender { + + func sendCrypto(crypto: CryptoSendingSpec, + chain: ChainSpec, + toAddress: HexAddress) async throws -> String { + guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { + throw CryptoSender.Error.sendingNotSupported + } + + let tx = try await createSendTransaction(crypto: crypto, + fromAddress: self.wallet.address, + toAddress: toAddress, + chainId: chain.id) + + guard wallet.walletState != .externalLinked else { + let response = try await wallet.sendViaWalletConnectTransaction(tx: tx, chainId: chain.id) + return response + } + + + let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) + return hash + } + + func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, + on chain: ChainSpec, + toAddress: HexAddress) async throws -> EVMCoinAmount { + + guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { + throw CryptoSender.Error.sendingNotSupported + } + + let transaction = try await createSendTransaction(crypto: maxCrypto, + fromAddress: self.wallet.address, + toAddress: toAddress, + chainId: chain.id) + + guard let gasPriceWei = transaction.gasPrice?.quantity else { + throw CryptoSender.Error.failedFetchGasPrice + } + let gasPrice = EVMCoinAmount(wei: gasPriceWei) + + let gas = transaction.gas?.quantity ?? defaultSendTxGasPrice + let gasFee = EVMCoinAmount(gwei: gasPrice.gwei * Double(gas)) + return gasFee + } + + func fetchGasPrices(on chain: ChainSpec) async throws -> EstimatedGasPrices { + try await fetchGasPrices(chainId: chain.id) + } + + func fetchGasPrice(chainId: Int, for speed: CryptoSendingSpec.TxSpeed) async throws -> EVMCoinAmount { + let prices: EstimatedGasPrices = try await fetchGasPrices(chainId: chainId) + return prices.getPriceForSpeed(speed) + } + + private func fetchGasPrices(chainId: Int) async throws -> EstimatedGasPrices { + // here routes to Status or Infura source + try await NetworkService().fetchInfuraGasPrices(chainId: chainId) + } +} + +struct NativeCoinCryptoSender: CryptoSenderProtocol, EVMCryptoSender { + let defaultSendTxGasPrice: BigUInt = 21_000 + let wallet: UDWallet + + func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { + // only native tokens supported + return (token == CryptoSender.SupportedToken.eth && chainType == .Ethereum) || + (token == CryptoSender.SupportedToken.matic && chainType == .Matic) + } + + internal func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction { + let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, + chainId: chainId) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + + let sender = EthereumAddress(hexString: fromAddress) + let receiver = EthereumAddress(hexString: toAddress) + + var transaction = EthereumTransaction(nonce: nonce, + gasPrice: try EthereumQuantity(speedBasedGasPrice.wei), + gas: try EthereumQuantity(defaultSendTxGasPrice), + from: sender, + to: receiver, + value: try EthereumQuantity(crypto.amount.getOnChainCountable()) + ) + + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + transaction.gas = gasEstimate + } + return transaction + } +} + +struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { + let defaultSendTxGasPrice: BigUInt = 100_000 + + let wallet: UDWallet + + func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { + // only native tokens supported + return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: + } + + internal func createSendTransaction(crypto: CryptoSendingSpec, + fromAddress: HexAddress, + toAddress: HexAddress, + chainId: Int) async throws -> EthereumTransaction { + let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, + chainId: chainId) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + + guard let sender = try? EthereumAddress(hex: fromAddress, eip55: false), + let receiver = try? EthereumAddress(hex: toAddress, eip55: false) else { + throw CryptoSender.Error.invalidAddresses + } + // Load ERC20 contract + let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) + let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address + guard let contractAddress = try? EthereumAddress(hex: usdtContractAddress, eip55: false) else { + throw CryptoSender.Error.invalidAddresses + } + + let erc20Contract = web3.eth.Contract(type: GenericERC20Contract.self, + address: contractAddress) + + guard let transactionCreated = erc20Contract + .transfer(to: receiver, value: crypto.amount.getOnChainCountable()) + .createTransaction(nonce: nonce, + from: sender, + value: 0, // zero native tokens transferred + gas: 100000, // TODO: + gasPrice: try EthereumQuantity(speedBasedGasPrice.wei)) else { + throw CryptoSender.Error.failedCreateSendTransaction + } + + var transaction = transactionCreated + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + transaction.gas = gasEstimate + } + return transaction + } +} From 85fe46be9ca6ea29182cad4c90d6f24b2faee444 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 13:09:27 +0300 Subject: [PATCH 18/23] multiple tokens allowed --- .../CryptoSender/CryptoSender+Entities.swift | 22 ++++++++++++++ .../CryptoSender/EVMCryptoSender.swift | 29 +++++++++---------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index a67a72861..3b05e99e4 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -123,6 +123,7 @@ struct CryptoSendingSpec { extension CryptoSender { enum Error: Swift.Error { case sendingNotSupported + case tokenNotSupportedOnChain case failedFetchGasPrice case failedCreateSendTransaction case insufficientFunds @@ -133,5 +134,26 @@ extension CryptoSender { case eth = "ETH" case matic = "MATIC" case usdt = "USDT" + + static let array: [CryptoSender.SupportedToken : + [BlockchainType : (mainnet: String, + testnet: String?)]] = + [ + .usdt: [.Ethereum: (mainnet: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + testnet: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0")], + + .usdt: [.Matic: (mainnet: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + testnet: nil)] // fake contracts only + ] + + func getContractAddress(for chain: ChainSpec) throws -> HexAddress { + guard let addresses = Self.array[self]?[chain.blockchainType] else { + throw CryptoSender.Error.tokenNotSupportedOnChain + } + guard let contract = chain.env == .mainnet ? addresses.mainnet : addresses.testnet else { + throw CryptoSender.Error.tokenNotSupportedOnChain + } + return contract + } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift index 598ad289b..7b8a446fc 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift @@ -20,7 +20,7 @@ protocol EVMCryptoSender: CryptoSenderProtocol { func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction + chain: ChainSpec) async throws -> EthereumTransaction } extension EVMCryptoSender { @@ -35,14 +35,13 @@ extension EVMCryptoSender { let tx = try await createSendTransaction(crypto: crypto, fromAddress: self.wallet.address, toAddress: toAddress, - chainId: chain.id) + chain: chain) guard wallet.walletState != .externalLinked else { let response = try await wallet.sendViaWalletConnectTransaction(tx: tx, chainId: chain.id) return response } - let hash = try await JRPC_Client.instance.sendTx(transaction: tx, udWallet: self.wallet, chainIdInt: chain.id) return hash } @@ -58,7 +57,7 @@ extension EVMCryptoSender { let transaction = try await createSendTransaction(crypto: maxCrypto, fromAddress: self.wallet.address, toAddress: toAddress, - chainId: chain.id) + chain: chain) guard let gasPriceWei = transaction.gasPrice?.quantity else { throw CryptoSender.Error.failedFetchGasPrice @@ -98,10 +97,10 @@ struct NativeCoinCryptoSender: CryptoSenderProtocol, EVMCryptoSender { internal func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction { + chain: ChainSpec) async throws -> EthereumTransaction { let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, - chainId: chainId) - let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + chainId: chain.id) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chain.id, for: crypto.speed) let sender = EthereumAddress(hexString: fromAddress) let receiver = EthereumAddress(hexString: toAddress) @@ -114,7 +113,7 @@ struct NativeCoinCryptoSender: CryptoSenderProtocol, EVMCryptoSender { value: try EthereumQuantity(crypto.amount.getOnChainCountable()) ) - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chain.id) { transaction.gas = gasEstimate } return transaction @@ -127,25 +126,25 @@ struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { let wallet: UDWallet func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { - // only native tokens supported return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: } internal func createSendTransaction(crypto: CryptoSendingSpec, fromAddress: HexAddress, toAddress: HexAddress, - chainId: Int) async throws -> EthereumTransaction { + chain: ChainSpec) async throws -> EthereumTransaction { let nonce: EthereumQuantity = try await JRPC_Client.instance.fetchNonce(address: fromAddress, - chainId: chainId) - let speedBasedGasPrice = try await fetchGasPrice(chainId: chainId, for: crypto.speed) + chainId: chain.id) + let speedBasedGasPrice = try await fetchGasPrice(chainId: chain.id, for: crypto.speed) guard let sender = try? EthereumAddress(hex: fromAddress, eip55: false), let receiver = try? EthereumAddress(hex: toAddress, eip55: false) else { throw CryptoSender.Error.invalidAddresses } // Load ERC20 contract - let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chainId) - let usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address + let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chain.id) + let usdtContractAddress = try crypto.token.getContractAddress(for: chain) + guard let contractAddress = try? EthereumAddress(hex: usdtContractAddress, eip55: false) else { throw CryptoSender.Error.invalidAddresses } @@ -164,7 +163,7 @@ struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { } var transaction = transactionCreated - if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chainId) { + if let gasEstimate = try? await JRPC_Client.instance.fetchGasLimit(transaction: transaction, chainId: chain.id) { transaction.gas = gasEstimate } return transaction From 68d91c3e27da8f645d81b78a64a47b326e38a341 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 13:32:49 +0300 Subject: [PATCH 19/23] protocol extended --- .../CryptoSender/CryptoSender+Entities.swift | 2 +- .../CryptoSender/CryptoSender.swift | 12 ++++++------ .../CryptoSender/CryptoSenderProtocol.swift | 2 +- .../CryptoSender/EVMCryptoSender.swift | 14 +++++++------- .../SendCryptoAsset/SendCryptoAssetViewModel.swift | 5 ++++- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index 3b05e99e4..70af74ac3 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -140,7 +140,7 @@ extension CryptoSender { testnet: String?)]] = [ .usdt: [.Ethereum: (mainnet: "0xdAC17F958D2ee523a2206206994597C13D831ec7", - testnet: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0")], + testnet: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0")], // sepolia .usdt: [.Matic: (mainnet: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", testnet: nil)] // fake contracts only diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift index 4678db3ad..3241aa589 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender.swift @@ -10,19 +10,19 @@ import Foundation struct CryptoSender: CryptoSenderProtocol { let wallet: UDWallet - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { + func canSendCrypto(token: CryptoSender.SupportedToken, chain: ChainSpec) -> Bool { // only native tokens supported for Ethereum and Polygon - return NativeCoinCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) || TokenCryptoSender(wallet: wallet).canSendCrypto(token: token, chainType: chainType) + return NativeCoinCryptoSender(wallet: wallet).canSendCrypto(token: token, chain: chain) || TokenCryptoSender(wallet: wallet).canSendCrypto(token: token, chain: chain) } func sendCrypto(crypto: CryptoSendingSpec, chain: ChainSpec, toAddress: HexAddress) async throws -> String { let cryptoSender: CryptoSenderProtocol = NativeCoinCryptoSender(wallet: wallet) - if cryptoSender.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { + if cryptoSender.canSendCrypto(token: crypto.token, chain: chain) { return try await cryptoSender.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) } let cryptoSender2: CryptoSenderProtocol = TokenCryptoSender(wallet: wallet) - if cryptoSender2.canSendCrypto(token: crypto.token, chainType: chain.blockchainType) { + if cryptoSender2.canSendCrypto(token: crypto.token, chain: chain) { return try await cryptoSender2.sendCrypto(crypto: crypto, chain: chain, toAddress: toAddress) } throw CryptoSender.Error.sendingNotSupported @@ -30,12 +30,12 @@ struct CryptoSender: CryptoSenderProtocol { func computeGasFeeFrom(maxCrypto: CryptoSendingSpec, on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMCoinAmount { let cryptoSender: CryptoSenderProtocol = NativeCoinCryptoSender(wallet: wallet) - if cryptoSender.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { + if cryptoSender.canSendCrypto(token: maxCrypto.token, chain: chain) { return try await cryptoSender.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) } let cryptoSender2: CryptoSenderProtocol = TokenCryptoSender(wallet: wallet) - if cryptoSender2.canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) { + if cryptoSender2.canSendCrypto(token: maxCrypto.token, chain: chain) { return try await cryptoSender2.computeGasFeeFrom(maxCrypto: maxCrypto, on: chain, toAddress: toAddress) } throw CryptoSender.Error.sendingNotSupported diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift index 4bc55d423..51b487f44 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSenderProtocol.swift @@ -15,7 +15,7 @@ protocol CryptoSenderProtocol { /// - token: tokan name /// - chain: chain /// - Returns: true if the sending is supported - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool + func canSendCrypto(token: CryptoSender.SupportedToken, chain: ChainSpec) -> Bool /// Create TX, send it to the chain and store it to the storage as 'pending'. /// Method fails if sending TX failed. Otherwise it returns TX hash diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift index 7b8a446fc..f904293ae 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift @@ -28,7 +28,7 @@ extension EVMCryptoSender { func sendCrypto(crypto: CryptoSendingSpec, chain: ChainSpec, toAddress: HexAddress) async throws -> String { - guard canSendCrypto(token: crypto.token, chainType: chain.blockchainType) else { + guard canSendCrypto(token: crypto.token, chain: chain) else { throw CryptoSender.Error.sendingNotSupported } @@ -50,7 +50,7 @@ extension EVMCryptoSender { on chain: ChainSpec, toAddress: HexAddress) async throws -> EVMCoinAmount { - guard canSendCrypto(token: maxCrypto.token, chainType: chain.blockchainType) else { + guard canSendCrypto(token: maxCrypto.token, chain: chain) else { throw CryptoSender.Error.sendingNotSupported } @@ -88,10 +88,10 @@ struct NativeCoinCryptoSender: CryptoSenderProtocol, EVMCryptoSender { let defaultSendTxGasPrice: BigUInt = 21_000 let wallet: UDWallet - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { + func canSendCrypto(token: CryptoSender.SupportedToken, chain: ChainSpec) -> Bool { // only native tokens supported - return (token == CryptoSender.SupportedToken.eth && chainType == .Ethereum) || - (token == CryptoSender.SupportedToken.matic && chainType == .Matic) + return (token == CryptoSender.SupportedToken.eth && chain.blockchainType == .Ethereum) || + (token == CryptoSender.SupportedToken.matic && chain.blockchainType == .Matic) } internal func createSendTransaction(crypto: CryptoSendingSpec, @@ -125,8 +125,8 @@ struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { let wallet: UDWallet - func canSendCrypto(token: CryptoSender.SupportedToken, chainType: BlockchainType) -> Bool { - return (token == CryptoSender.SupportedToken.usdt && chainType == .Ethereum) // TODO: + func canSendCrypto(token: CryptoSender.SupportedToken, chain: ChainSpec) -> Bool { + return (try? token.getContractAddress(for: chain)) != nil } internal func createSendTransaction(crypto: CryptoSendingSpec, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift index bc1e615a6..0291e5815 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift @@ -80,7 +80,10 @@ final class SendCryptoAssetViewModel: ObservableObject { guard let supportedToken = try? getSupportedTokenFor(balanceToken: token), let chainType = token.blockchainType else { return false } - return cryptoSender.canSendCrypto(token: supportedToken, chainType: chainType) + let env: UnsConfigManager.BlockchainEnvironment = User.instance.getSettings().isTestnetUsed ? .testnet : .mainnet + return cryptoSender.canSendCrypto(token: supportedToken, + chain: ChainSpec(blockchainType: chainType, + env: env)) } func sendCryptoTokenWith(sendData: SendCryptoAsset.SendTokenAssetData, From 6afde0a2f3fb5ed4cf4987c98885c243eb25c2c1 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 13:38:38 +0300 Subject: [PATCH 20/23] refactoring --- .../SendCryptoAsset/CryptoSender/EVMCryptoSender.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift index f904293ae..74b0f037c 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/EVMCryptoSender.swift @@ -141,11 +141,11 @@ struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { let receiver = try? EthereumAddress(hex: toAddress, eip55: false) else { throw CryptoSender.Error.invalidAddresses } - // Load ERC20 contract + let web3 = try JRPC_Client.instance.getWeb3(chainIdInt: chain.id) - let usdtContractAddress = try crypto.token.getContractAddress(for: chain) + let tokenContractAddress = try crypto.token.getContractAddress(for: chain) - guard let contractAddress = try? EthereumAddress(hex: usdtContractAddress, eip55: false) else { + guard let contractAddress = try? EthereumAddress(hex: tokenContractAddress, eip55: false) else { throw CryptoSender.Error.invalidAddresses } @@ -157,7 +157,7 @@ struct TokenCryptoSender: CryptoSenderProtocol, EVMCryptoSender { .createTransaction(nonce: nonce, from: sender, value: 0, // zero native tokens transferred - gas: 100000, // TODO: + gas: try EthereumQuantity(defaultSendTxGasPrice), gasPrice: try EthereumQuantity(speedBasedGasPrice.wei)) else { throw CryptoSender.Error.failedCreateSendTransaction } From c49384d0b3a30ea861b3c112304f5ba355c80b73 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 15:57:21 +0300 Subject: [PATCH 21/23] added BNB, USDC, WETH --- .../CryptoSender/CryptoSender+Entities.swift | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index 70af74ac3..b31328e83 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -134,16 +134,46 @@ extension CryptoSender { case eth = "ETH" case matic = "MATIC" case usdt = "USDT" + case usdc = "USDC" + case bnb = "BNB" + case weth = "WETH" static let array: [CryptoSender.SupportedToken : [BlockchainType : (mainnet: String, - testnet: String?)]] = + testnet: String?, + decimals: UInt8)]] = [ - .usdt: [.Ethereum: (mainnet: "0xdAC17F958D2ee523a2206206994597C13D831ec7", - testnet: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0")], // sepolia + .usdt: [.Ethereum: (mainnet: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + testnet: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0", // sepolia + decimals: 6)], - .usdt: [.Matic: (mainnet: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", - testnet: nil)] // fake contracts only + .usdt: [.Matic: (mainnet: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + testnet: nil, // amoy + decimals: 6)], + + .bnb: [.Ethereum: (mainnet: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", + testnet: nil, // sepolia + decimals: 18)], + + .bnb: [.Matic: (mainnet: "0x3BA4c387f786bFEE076A58914F5Bd38d668B42c3", + testnet: nil, // amoy + decimals: 18)], + + .usdc: [.Ethereum: (mainnet: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + testnet: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // sepolia + decimals: 6)], + + .usdc: [.Matic: (mainnet: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + testnet: nil, // amoy + decimals: 6)], + + .weth: [.Ethereum: (mainnet: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + testnet: "0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9", // sepolia + decimals: 18)], + + .weth: [.Matic: (mainnet: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + testnet: nil, // amoy + decimals: 18)], ] func getContractAddress(for chain: ChainSpec) throws -> HexAddress { From 9ef97c10fa5b60225b99c5c9963a82075dfada52 Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 17:03:45 +0300 Subject: [PATCH 22/23] ERC29Token --- .../CryptoSender/CryptoSender+Entities.swift | 24 +++++++++++++++---- .../SendCryptoAssetViewModel.swift | 6 ++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index b31328e83..a5f7f45e2 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -51,10 +51,12 @@ protocol ERC20TokenAmount { var decimals: UInt8 { get } } -struct USDT: ERC20TokenAmount, OnChainCountable { +struct ERC20Token: ERC20TokenAmount, OnChainCountable { var elementaryUnits: UDBigUInt + var decimals: UInt8 - init(units: Double) { + init(units: Double, decimals: UInt8) { + self.decimals = decimals self.elementaryUnits = UDBigUInt(units * pow(10, Double(decimals))) } @@ -62,7 +64,6 @@ struct USDT: ERC20TokenAmount, OnChainCountable { elementaryUnits } - let decimals: UInt8 = 6 var units: Double { Double(elementaryUnits) / pow(10, Double(decimals)) @@ -113,9 +114,14 @@ struct CryptoSendingSpec { let amount: OnChainCountable let speed: TxSpeed - init(token: CryptoSender.SupportedToken, amount: OnChainCountable, speed: TxSpeed = .normal) { + init(token: CryptoSender.SupportedToken, units: Double, speed: TxSpeed = .normal) throws { + + switch token { + case .eth, .matic: self.amount = EVMCoinAmount(units: units) + default: self.amount = ERC20Token(units: units, decimals: try token.getContractDecimals(for: .Ethereum)) + } + self.token = token - self.amount = amount self.speed = speed } } @@ -124,6 +130,7 @@ extension CryptoSender { enum Error: Swift.Error { case sendingNotSupported case tokenNotSupportedOnChain + case decimalsNotIdentified case failedFetchGasPrice case failedCreateSendTransaction case insufficientFunds @@ -185,5 +192,12 @@ extension CryptoSender { } return contract } + + func getContractDecimals(for chainType: BlockchainType) throws -> UInt8 { + guard let decimals = Self.array[self]?[chainType]?.decimals else { + throw CryptoSender.Error.decimalsNotIdentified + } + return decimals + } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift index 0291e5815..7c3906a79 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/SendCryptoAssetViewModel.swift @@ -146,11 +146,9 @@ final class SendCryptoAssetViewModel: ObservableObject { tokenAmount: Double, txSpeed: SendCryptoAsset.TransactionSpeed) throws -> CryptoSendingSpec { let token = try getSupportedTokenFor(balanceToken: token) - let amount = EVMCoinAmount(units: tokenAmount) let speed = getSpecTransactionSpeedFor(txSpeed: txSpeed) - - return CryptoSendingSpec(token: token, - amount: amount, + return try CryptoSendingSpec(token: token, + units: tokenAmount, speed: speed) } From 2c52d80aa9d0943a29b37b35ae9cb1206acf25cd Mon Sep 17 00:00:00 2001 From: rommex Date: Mon, 22 Apr 2024 17:09:23 +0300 Subject: [PATCH 23/23] refactoring --- .../SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift index a5f7f45e2..453870770 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/SendCryptoAsset/CryptoSender/CryptoSender+Entities.swift @@ -47,11 +47,11 @@ struct EVMCoinAmount: OnChainCountable { } } -protocol ERC20TokenAmount { +protocol DecimalPointFloatable { var decimals: UInt8 { get } } -struct ERC20Token: ERC20TokenAmount, OnChainCountable { +struct ERC20Token: DecimalPointFloatable, OnChainCountable { var elementaryUnits: UDBigUInt var decimals: UInt8 @@ -64,7 +64,6 @@ struct ERC20Token: ERC20TokenAmount, OnChainCountable { elementaryUnits } - var units: Double { Double(elementaryUnits) / pow(10, Double(decimals)) }