From 219238a95222e41b62bb3a4101d1f90f825036e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rich=CE=9Brd?= Date: Fri, 10 Sep 2021 13:27:49 -0400 Subject: [PATCH] feat: eip1559 (#12) --- status/ens.nim | 8 +- status/libstatus/conversions.nim | 10 ++- status/libstatus/eth/eth.nim | 6 +- status/libstatus/eth/methods.nim | 8 +- status/libstatus/eth/transactions.nim | 8 +- status/libstatus/wallet.nim | 18 +++- status/provider.nim | 12 ++- status/signals/wallet.nim | 2 + status/status.nim | 2 +- status/stickers.nim | 11 ++- status/transactions.nim | 18 ++-- status/types/transaction.nim | 15 +++- status/utils.nim | 16 +++- status/wallet.nim | 118 +++++++++++++++++++------- 14 files changed, 182 insertions(+), 70 deletions(-) diff --git a/status/ens.nim b/status/ens.nim index b3c1c43..d6bb5b6 100644 --- a/status/ens.nim +++ b/status/ens.nim @@ -246,7 +246,7 @@ proc registerUsernameEstimateGas*(username: string, address: string, pubKey: str if success: result = fromHex[int](response) -proc registerUsername*(username, pubKey, address, gas, gasPrice, password: string, success: var bool): string = +proc registerUsername*(username, pubKey, address, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): string = let label = fromHex(FixedBytes[32], label(username)) coordinates = extractCoordinates(pubkey) @@ -261,7 +261,7 @@ proc registerUsername*(username, pubKey, address, gas, gasPrice, password: stri registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register) approveAndCallObj = ApproveAndCall[132](to: ensUsernamesContract.address, value: price, data: DynamicBytes[132].fromHex(registerAbiEncoded)) - var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, gas, gasPrice) + var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) result = sntContract.methods["approveAndCall"].send(tx, approveAndCallObj, password, success) if success: @@ -288,7 +288,7 @@ proc setPubKeyEstimateGas*(username: string, address: string, pubKey: string, su except RpcException as e: raise -proc setPubKey*(username, pubKey, address, gas, gasPrice, password: string, success: var bool): string = +proc setPubKey*(username, pubKey, address, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): string = var hash = namehash(username) hash.removePrefix("0x") @@ -300,7 +300,7 @@ proc setPubKey*(username, pubKey, address, gas, gasPrice, password: string, succ setPubkey = SetPubkey(label: label, x: x, y: y) resolverAddress = resolver(hash) - var tx = transactions.buildTokenTransaction(parseAddress(address), parseAddress(resolverAddress), gas, gasPrice) + var tx = transactions.buildTokenTransaction(parseAddress(address), parseAddress(resolverAddress), gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) try: result = resolverContract.methods["setPubkey"].send(tx, setPubkey, password, success) diff --git a/status/libstatus/conversions.nim b/status/libstatus/conversions.nim index 5eee546..b941cd1 100644 --- a/status/libstatus/conversions.nim +++ b/status/libstatus/conversions.nim @@ -3,6 +3,8 @@ import import web3/[conversions, ethtypes], stint + +import ../types/transaction # TODO: make this public in nim-web3 lib template stripLeadingZeros*(value: string): string = @@ -12,16 +14,20 @@ template stripLeadingZeros*(value: string): string = cidx.inc value[cidx .. ^1] -# TODO: update this in nim-web3 -proc `%`*(x: EthSend): JsonNode = +proc `%`*(x: TransactionData): JsonNode = result = newJobject() result["from"] = %x.source + result["type"] = %x.txType if x.to.isSome: result["to"] = %x.to.unsafeGet if x.gas.isSome: result["gas"] = %x.gas.unsafeGet if x.gasPrice.isSome: result["gasPrice"] = %("0x" & x.gasPrice.unsafeGet.toHex.stripLeadingZeros) + if x.maxFeePerGas.isSome: + result["maxFeePerGas"] = %("0x" & x.maxFeePerGas.unsafeGet.toHex) + if x.maxPriorityFeePerGas.isSome: + result["maxPriorityFeePerGas"] = %("0x" & x.maxPriorityFeePerGas.unsafeGet.toHex) if x.value.isSome: result["value"] = %("0x" & x.value.unsafeGet.toHex) result["data"] = %x.data diff --git a/status/libstatus/eth/eth.nim b/status/libstatus/eth/eth.nim index 23e442c..9eb624e 100644 --- a/status/libstatus/eth/eth.nim +++ b/status/libstatus/eth/eth.nim @@ -2,9 +2,9 @@ import web3/ethtypes import - transactions, ../../types/[rpc_response] + transactions, ../../types/[rpc_response, transaction] -proc sendTransaction*(tx: var EthSend, password: string, success: var bool): string = +proc sendTransaction*(tx: var TransactionData, password: string, success: var bool): string = success = true try: let response = transactions.sendTransaction(tx, password) @@ -13,7 +13,7 @@ proc sendTransaction*(tx: var EthSend, password: string, success: var bool): str success = false result = e.msg -proc estimateGas*(tx: var EthSend, success: var bool): string = +proc estimateGas*(tx: var TransactionData, success: var bool): string = success = true try: let response = transactions.estimateGas(tx) diff --git a/status/libstatus/eth/methods.nim b/status/libstatus/eth/methods.nim index 85653f4..4f2cd6d 100644 --- a/status/libstatus/eth/methods.nim +++ b/status/libstatus/eth/methods.nim @@ -5,7 +5,7 @@ import nimcrypto, web3/[encoding, ethtypes] import - ../../types/[rpc_response], ../coder, eth, transactions + ../../types/[rpc_response, transaction], ../coder, eth, transactions export sendTransaction @@ -38,7 +38,7 @@ proc encodeAbi*(self: Method, obj: object = RootObj()): string = result &= encoded.data result &= data -proc estimateGas*(self: Method, tx: var EthSend, methodDescriptor: object, success: var bool): string = +proc estimateGas*(self: Method, tx: var TransactionData, methodDescriptor: object, success: var bool): string = success = true tx.data = self.encodeAbi(methodDescriptor) try: @@ -48,11 +48,11 @@ proc estimateGas*(self: Method, tx: var EthSend, methodDescriptor: object, succe success = false result = e.msg -proc send*(self: Method, tx: var EthSend, methodDescriptor: object, password: string, success: var bool): string = +proc send*(self: Method, tx: var TransactionData, methodDescriptor: object, password: string, success: var bool): string = tx.data = self.encodeAbi(methodDescriptor) result = eth.sendTransaction(tx, password, success) -proc call*[T](self: Method, tx: var EthSend, methodDescriptor: object, success: var bool): T = +proc call*[T](self: Method, tx: var TransactionData, methodDescriptor: object, success: var bool): T = success = true tx.data = self.encodeAbi(methodDescriptor) let response: RpcResponse diff --git a/status/libstatus/eth/transactions.nim b/status/libstatus/eth/transactions.nim index ee647ff..7d7cddc 100644 --- a/status/libstatus/eth/transactions.nim +++ b/status/libstatus/eth/transactions.nim @@ -5,9 +5,9 @@ import json_serialization, chronicles, web3/ethtypes import - ../core, ../../types/[rpc_response], ../conversions + ../core, ../../types/[rpc_response, transaction], ../conversions -proc estimateGas*(tx: EthSend): RpcResponse = +proc estimateGas*(tx: TransactionData): RpcResponse = let response = core.callPrivateRPC("eth_estimateGas", %*[%tx]) result = Json.decode(response, RpcResponse) if not result.error.isNil: @@ -15,7 +15,7 @@ proc estimateGas*(tx: EthSend): RpcResponse = trace "Gas estimated succesfully", estimate=result.result -proc sendTransaction*(tx: EthSend, password: string): RpcResponse = +proc sendTransaction*(tx: TransactionData, password: string): RpcResponse = let responseStr = core.sendTransaction($(%tx), password) result = Json.decode(responseStr, RpcResponse) if not result.error.isNil: @@ -23,7 +23,7 @@ proc sendTransaction*(tx: EthSend, password: string): RpcResponse = trace "Transaction sent succesfully", hash=result.result -proc call*(tx: EthSend): RpcResponse = +proc call*(tx: TransactionData): RpcResponse = let responseStr = core.callPrivateRPC("eth_call", %*[%tx, "latest"]) result = Json.decode(responseStr, RpcResponse) if not result.error.isNil: diff --git a/status/libstatus/wallet.nim b/status/libstatus/wallet.nim index de5b37f..4d9aba1 100644 --- a/status/libstatus/wallet.nim +++ b/status/libstatus/wallet.nim @@ -144,4 +144,20 @@ proc fetchCryptoServices*(success: var bool): string = except Exception as e: success = false error "Error getting crypto services: ", msg = e.msg - result = "" \ No newline at end of file + result = "" + +proc maxPriorityFeePerGas*(): string = + let payload = %* [] + result = callPrivateRPC("eth_maxPriorityFeePerGas", payload) + +proc suggestFees*(): string = + let payload = %* [] + result = callPrivateRPC("wallet_suggestFees", payload) + +proc feeHistory*(n: int): string = + let payload = %* [n, "latest", nil] + result = callPrivateRPC("eth_feeHistory", payload) + +proc getGasPrice*(): string = + let payload = %* [] + result = callPrivateRPC("eth_gasPrice", payload) diff --git a/status/provider.nim b/status/provider.nim index ae22604..1b64755 100644 --- a/status/provider.nim +++ b/status/provider.nim @@ -56,11 +56,13 @@ const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"]) type ProviderModel* = ref object events*: EventEmitter permissions*: PermissionsModel + wallet*: WalletModel -proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel): ProviderModel = +proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel, wallet: WalletModel): ProviderModel = result = ProviderModel() result.events = events result.permissions = permissions + result.wallet = wallet proc requestType(message: string): RequestTypes = let data = message.parseJson @@ -114,6 +116,8 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string = let password = request["password"].getStr() let selectedGasLimit = request["selectedGasLimit"].getStr() let selectedGasPrice = request["selectedGasPrice"].getStr() + let selectedTipLimit = request{"selectedTipLimit"}.getStr() + let selectedOverallLimit = request{"selectedOverallLimit"}.getStr() let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull): request["params"][0]["data"].getStr() else: @@ -124,8 +128,10 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string = var response = "" var validInput: bool = true + let eip1559Enabled = self.wallet.isEIP1559Enabled() + try: - validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, "dummy") + validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy") except Exception as e: validInput = false success = false @@ -133,7 +139,7 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string = if validInput: # TODO make this async - response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success, txData) + response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, eip1559Enabled, selectedTipLimit, selectedOverallLimit, password, success, txData) errorMessage = if not success: if response == "": "web3-response-error" diff --git a/status/signals/wallet.nim b/status/signals/wallet.nim index bb3cc14..3a8c25f 100644 --- a/status/signals/wallet.nim +++ b/status/signals/wallet.nim @@ -8,6 +8,7 @@ type WalletSignal* = ref object of Signal eventType*: string blockNumber*: int accounts*: seq[string] + baseFeePerGas*: string # newTransactions*: ??? erc20*: bool @@ -18,6 +19,7 @@ proc fromEvent*(T: type WalletSignal, jsonSignal: JsonNode): WalletSignal = if jsonSignal["event"].kind != JNull: result.eventType = jsonSignal["event"]["type"].getStr result.blockNumber = jsonSignal["event"]{"blockNumber"}.getInt + result.baseFeePerGas = jsonSignal["event"]{"baseFeePerGas"}.getStr result.erc20 = jsonSignal["event"]{"erc20"}.getBool result.accounts = @[] if jsonSignal["event"]["accounts"].kind != JNull: diff --git a/status/status.nim b/status/status.nim index 044c934..3ae9d1f 100644 --- a/status/status.nim +++ b/status/status.nim @@ -50,7 +50,7 @@ proc newStatusInstance*(fleetConfig: string): Status = result.mailservers = mailservers.newMailserversModel(result.events) result.browser = browser.newBrowserModel(result.events) result.tokens = tokens.newTokensModel(result.events) - result.provider = provider.newProviderModel(result.events, result.permissions) + result.provider = provider.newProviderModel(result.events, result.permissions, result.wallet) result.osnotifications = newOsNotifications(result.events) proc initNode*(self: Status, statusGoDir, keystoreDir: string) = diff --git a/status/stickers.nim b/status/stickers.nim index fae2d6d..510b403 100644 --- a/status/stickers.nim +++ b/status/stickers.nim @@ -43,14 +43,14 @@ proc init*(self: StickersModel) = var evArgs = StickerArgs(e) self.addStickerToRecent(evArgs.sticker, evArgs.save) -proc buildTransaction(packId: Uint256, address: Address, price: Uint256, approveAndCall: var ApproveAndCall[100], sntContract: var Erc20Contract, gas = "", gasPrice = ""): EthSend = +proc buildTransaction(packId: Uint256, address: Address, price: Uint256, approveAndCall: var ApproveAndCall[100], sntContract: var Erc20Contract, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = ""): TransactionData = sntContract = status_contracts.getSntContract() let stickerMktContract = status_contracts.getContract("sticker-market") buyToken = BuyToken(packId: packId, address: address, price: price) buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken) approveAndCall = ApproveAndCall[100](to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded)) - transactions.buildTokenTransaction(address, sntContract.address, gas, gasPrice) + transactions.buildTokenTransaction(address, sntContract.address, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) proc estimateGas*(packId: int, address: string, price: string, success: var bool): int = var @@ -68,7 +68,7 @@ proc estimateGas*(packId: int, address: string, price: string, success: var bool if success: result = fromHex[int](response) -proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, password: string, success: var bool): string = +proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): string = var sntContract: Erc20Contract approveAndCall: ApproveAndCall[100] @@ -79,7 +79,10 @@ proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, p approveAndCall, sntContract, gas, - gasPrice + gasPrice, + isEIP1559Enabled, + maxPriorityFeePerGas, + maxFeePerGas ) result = sntContract.methods["approveAndCall"].send(tx, approveAndCall, password, success) diff --git a/status/transactions.nim b/status/transactions.nim index c418010..fec68ea 100644 --- a/status/transactions.nim +++ b/status/transactions.nim @@ -2,19 +2,25 @@ import options, strutils import - stint, web3/ethtypes + stint, web3/ethtypes, types/transaction from utils as status_utils import toUInt64, gwei2Wei, parseAddress -proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", data = ""): EthSend = - result = EthSend( +proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionData = + result = TransactionData( source: source, value: value.some, gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some), gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some), data: data ) + if isEIP1559Enabled: + result.txType = "0x02" + result.maxPriorityFeePerGas = if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxPriorityFeePerGas)).some + result.maxFeePerGas = (if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxFeePerGas)).some) + else: + result.txType = "0x00" -proc buildTokenTransaction*(source, contractAddress: Address, gas = "", gasPrice = ""): EthSend = - result = buildTransaction(source, 0.u256, gas, gasPrice) - result.to = contractAddress.some \ No newline at end of file +proc buildTokenTransaction*(source, contractAddress: Address, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = ""): TransactionData = + result = buildTransaction(source, 0.u256, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) + result.to = contractAddress.some diff --git a/status/types/transaction.nim b/status/types/transaction.nim index d86d77d..b2cb98c 100644 --- a/status/types/transaction.nim +++ b/status/types/transaction.nim @@ -1,7 +1,7 @@ {.used.} import strutils - +import web3/ethtypes, options, stint include pending_transaction_type type @@ -22,6 +22,19 @@ type fromAddress*: string to*: string +type + TransactionData* = object + source*: Address # the address the transaction is send from. + to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to. + gas*: Option[Quantity] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas. + gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas. + maxPriorityFeePerGas*: Option[Uint256] + maxFeePerGas*: Option[Uint256] + value*: Option[Uint256] # (optional) integer of the value sent with this transaction. + data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI. + nonce*: Option[Nonce] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce + txType*: string + proc cmpTransactions*(x, y: Transaction): int = # Sort proc to compare transactions from a single account. # Compares first by block number, then by nonce diff --git a/status/utils.nim b/status/utils.nim index d0720a0..15ccb7c 100644 --- a/status/utils.nim +++ b/status/utils.nim @@ -87,7 +87,9 @@ proc wei2Eth*(input: string, decimals: int): string = except Exception as e: error "Error parsing this wei value", input, msg=e.msg result = "0" - + +proc wei2Gwei*(input: string): string = + result = wei2Eth(input, 9) proc first*(jArray: JsonNode, fieldName, id: string): JsonNode = if jArray == nil: @@ -139,13 +141,19 @@ proc isAddress*(strAddress: string): bool = return false return true -proc validateTransactionInput*(from_addr, to_addr, assetAddress, value, gas, gasPrice, data, uuid: string) = +proc validateTransactionInput*(from_addr, to_addr, assetAddress, value, gas, gasPrice, data: string, isEIP1599Enabled: bool, maxPriorityFeePerGas, maxFeePerGas, uuid: string) = if not isAddress(from_addr): raise newException(ValueError, "from_addr is not a valid ETH address") if not isAddress(to_addr): raise newException(ValueError, "to_addr is not a valid ETH address") if parseFloat(value) < 0: raise newException(ValueError, "value should be a number >= 0") if parseInt(gas) <= 0: raise newException(ValueError, "gas should be a number > 0") - if parseFloat(gasPrice) <= 0: raise newException(ValueError, "gasPrice should be a number > 0") - + if isEIP1599Enabled: + if gasPrice != "" and (maxPriorityFeePerGas != "" or maxFeePerGas != ""): + raise newException(ValueError, "gasPrice can't be used with maxPriorityFeePerGas and maxFeePerGas") + if gasPrice == "": + if parseFloat(maxPriorityFeePerGas) <= 0: raise newException(ValueError, "maxPriorityFeePerGas should be a number > 0") + if parseFloat(maxFeePerGas) <= 0: raise newException(ValueError, "maxFeePerGas should be a number > 0") + else: + if parseFloat(gasPrice) <= 0: raise newException(ValueError, "gasPrice should be a number > 0") if uuid.isEmptyOrWhitespace(): raise newException(ValueError, "uuid is required") if assetAddress != "": # If a token is being used diff --git a/status/wallet.nim b/status/wallet.nim index 753b397..dcdd7a0 100644 --- a/status/wallet.nim +++ b/status/wallet.nim @@ -1,6 +1,6 @@ import json, strformat, strutils, chronicles, sequtils, sugar, httpclient, tables, net -import json_serialization, stint -from web3/ethtypes import Address, EthSend, Quantity +import json_serialization, stint, stew/byteutils, algorithm +from web3/ethtypes import Address, Quantity from web3/conversions import `$` from libstatus/core import getBlockByNumber import libstatus/accounts as status_accounts @@ -10,7 +10,7 @@ import libstatus/wallet as status_wallet import libstatus/accounts/constants as constants import libstatus/eth/[eth, contracts] from libstatus/core import getBlockByNumber -from utils as libstatus_utils import eth2Wei, gwei2Wei, first, toUInt64, parseAddress +from utils as libstatus_utils import eth2Wei, gwei2Wei, wei2Gwei, first, toUInt64, parseAddress import wallet/[balance_manager, collectibles] import wallet/account as wallet_account import transactions @@ -38,6 +38,8 @@ type WalletModel* = ref object defaultCurrency*: string tokens*: seq[Erc20Contract] totalBalance*: float + eip1559Enabled*: bool + latestBaseFee*: string proc getDefaultCurrency*(self: WalletModel): string proc calculateTotalFiatBalance*(self: WalletModel) @@ -49,6 +51,7 @@ proc newWalletModel*(events: EventEmitter): WalletModel = result.events = events result.defaultCurrency = "" result.totalBalance = 0.0 + result.eip1559Enabled = false proc initEvents*(self: WalletModel) = self.events.on("currencyChanged") do(e: Args): @@ -64,12 +67,12 @@ proc initEvents*(self: WalletModel) = proc delete*(self: WalletModel) = discard -proc buildTokenTransaction(source, to, assetAddress: Address, value: float, transfer: var Transfer, contract: var Erc20Contract, gas = "", gasPrice = ""): EthSend = +proc buildTokenTransaction(source, to, assetAddress: Address, value: float, transfer: var Transfer, contract: var Erc20Contract, gas = "", gasPrice = "", isEIP1559Enabled: bool = false, maxPriorityFeePerGas = "", maxFeePerGas = ""): TransactionData = contract = getErc20Contract(assetAddress) if contract == nil: raise newException(ValueError, fmt"Could not find ERC-20 contract with address '{assetAddress}' for the current network") transfer = Transfer(to: to, value: eth2Wei(value, contract.decimals)) - transactions.buildTokenTransaction(source, assetAddress, gas, gasPrice) + transactions.buildTokenTransaction(source, assetAddress, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) proc getKnownTokenContract*(self: WalletModel, address: Address): Erc20Contract = getErc20Contracts().concat(getCustomTokens()).getErc20ContractByAddress(address) @@ -99,15 +102,17 @@ proc confirmTransactionStatus(self: WalletModel, pendingTransactions: JsonNode, ) self.events.emit(parseEnum[PendingTransactionType](trx["type"].getStr).confirmed, ev) -proc getLatestBlockNumber*(self: WalletModel): int = +proc getLatestBlock*(): tuple[blockNumber: int, baseFee: string] = let response = getBlockByNumber("latest").parseJson() - if not response.hasKey("result"): - return -1 + if response.hasKey("result"): + let blockNumber = parseInt($fromHex(Stuint[256], response["result"]["number"].getStr)) + let baseFee = $fromHex(Stuint[256], response["result"]{"baseFeePerGas"}.getStr) + return (blockNumber, baseFee) + return (-1, "") - return parseInt($fromHex(Stuint[256], response["result"]["number"].getStr)) +proc getLatestBlockNumber*(self: WalletModel): int = getLatestBlock()[0] -proc checkPendingTransactions*(self: WalletModel) = - let latestBlockNumber = self.getLatestBlockNumber() +proc checkPendingTransactions*(self: WalletModel, latestBlockNumber: int) = if latestBlockNumber == -1: return @@ -133,10 +138,10 @@ proc estimateTokenGas*(self: WalletModel, source, to, assetAddress, value: strin result = contract.methods["transfer"].estimateGas(tx, transfer, success) -proc sendTransaction*(source, to, value, gas, gasPrice, password: string, success: var bool, data = ""): string = +proc sendTransaction*(source, to, value, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas, maxFeePerGas, password: string, success: var bool, data = ""): string = var tx = transactions.buildTransaction( parseAddress(source), - eth2Wei(parseFloat(value), 18), gas, gasPrice, data + eth2Wei(parseFloat(value), 18), gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas, data ) if to != "": @@ -146,7 +151,7 @@ proc sendTransaction*(source, to, value, gas, gasPrice, password: string, succes if success: trackPendingTransaction(result, $source, $to, PendingTransactionType.WalletTransfer, "") -proc sendTokenTransaction*(source, to, assetAddress, value, gas, gasPrice, password: string, success: var bool): string = +proc sendTokenTransaction*(source, to, assetAddress, value, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas, maxFeePerGas, password: string, success: var bool): string = var transfer: Transfer contract: Erc20Contract @@ -158,7 +163,8 @@ proc sendTokenTransaction*(source, to, assetAddress, value, gas, gasPrice, passw transfer, contract, gas, - gasPrice + gasPrice, + isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas ) result = contract.methods["transfer"].send(tx, transfer, password, success) @@ -217,6 +223,37 @@ proc newAccount*(self: WalletModel, walletType: string, derivationPath: string, updateBalance(account, self.getDefaultCurrency()) account +proc maxPriorityFeePerGas*(self: WalletModel):string = + let response = status_wallet.maxPriorityFeePerGas().parseJson() + if response.hasKey("result"): + return $fromHex(Stuint[256], response["result"].getStr) + else: + error "Error obtaining max priority fee per gas", error=response + raise newException(StatusGoException, "Error obtaining max priority fee per gas") + +proc suggestFees*(self: WalletModel):JsonNode = + let response = status_wallet.suggestFees().parseJson() + if response.hasKey("result"): + return response["result"].getElems()[0] + else: + error "Error obtaining suggested fees", error=response + raise newException(StatusGoException, "Error obtaining suggested fees") + +proc cmpUint256(x, y: Uint256): int = + if x > y: 1 + elif x == y: 0 + else: -1 + +proc feeHistory*(self: WalletModel, n:int):seq[Uint256] = + let response = status_wallet.feeHistory(101).parseJson() + if response.hasKey("result"): + for it in response["result"]["baseFeePerGas"]: + result.add(fromHex(Stuint[256], it.getStr)) + result.sort(cmpUint256) + else: + error "Error obtaining fee history", error=response + raise newException(StatusGoException, "Error obtaining fee history") + proc initAccounts*(self: WalletModel) = self.tokens = status_tokens.getVisibleTokens() let accounts = status_wallet.getWalletAccounts() @@ -354,23 +391,6 @@ proc getTransfersByAddress*(self: WalletModel, address: string, toBlock: Uint256 proc validateMnemonic*(self: WalletModel, mnemonic: string): string = result = status_wallet.validateMnemonic(mnemonic).parseJSON()["error"].getStr -proc getGasPricePredictions*(): GasPricePrediction = - if status_settings.getCurrentNetwork() != NetworkType.Mainnet: - # TODO: what about other chains like xdai? - return GasPricePrediction(safeLow: 1.0, standard: 2.0, fast: 3.0, fastest: 4.0) - let secureSSLContext = newContext() - let client = newHttpClient(sslContext = secureSSLContext) - try: - let url: string = fmt"https://etherchain.org/api/gasPriceOracle" - client.headers = newHttpHeaders({ "Content-Type": "application/json" }) - let response = client.request(url) - result = Json.decode(response.body, GasPricePrediction) - except Exception as e: - echo "error getting gas price predictions" - echo e.msg - finally: - client.close() - proc checkRecentHistory*(self: WalletModel, addresses: seq[string]): string = result = status_wallet.checkRecentHistory(addresses) @@ -405,4 +425,36 @@ proc getOpenseaCollections*(address: string): string = result = status_wallet.getOpenseaCollections(address) proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string = - result = status_wallet.getOpenseaAssets(address, collectionSlug, limit) \ No newline at end of file + result = status_wallet.getOpenseaAssets(address, collectionSlug, limit) + +proc getGasPrice*(self: WalletModel): string = + let response = status_wallet.getGasPrice().parseJson + if response.hasKey("result"): + return $fromHex(Stuint[256], response["result"].getStr) + else: + error "Error obtaining max priority fee per gas", error=response + raise newException(StatusGoException, "Error obtaining gas price") + + +proc setLatestBaseFee*(self: WalletModel, latestBaseFee: string) = + self.latestBaseFee = latestBaseFee + +proc getLatestBaseFee*(self: WalletModel): string = + result = self.latestBaseFee + +proc isEIP1559Enabled*(self: WalletModel, blockNumber: int):bool = + let networkId = status_settings.getCurrentNetworkDetails().config.networkId + let activationBlock = case status_settings.getCurrentNetworkDetails().config.networkId: + of 3: 10499401 # Ropsten + of 4: 8897988 # Rinkeby + of 5: 5062605 # Goerli + of 1: 12965000 # Mainnet + else: -1 + if activationBlock > -1 and blockNumber >= activationBlock: + result = true + else: + result = false + self.eip1559Enabled = result + +proc isEIP1559Enabled*(self: WalletModel): bool = + result = self.eip1559Enabled