feat: eip1559 (#12)
This commit is contained in:
parent
efe2790db6
commit
219238a952
|
@ -246,7 +246,7 @@ proc registerUsernameEstimateGas*(username: string, address: string, pubKey: str
|
||||||
if success:
|
if success:
|
||||||
result = fromHex[int](response)
|
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
|
let
|
||||||
label = fromHex(FixedBytes[32], label(username))
|
label = fromHex(FixedBytes[32], label(username))
|
||||||
coordinates = extractCoordinates(pubkey)
|
coordinates = extractCoordinates(pubkey)
|
||||||
|
@ -261,7 +261,7 @@ proc registerUsername*(username, pubKey, address, gas, gasPrice, password: stri
|
||||||
registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register)
|
registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register)
|
||||||
approveAndCallObj = ApproveAndCall[132](to: ensUsernamesContract.address, value: price, data: DynamicBytes[132].fromHex(registerAbiEncoded))
|
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)
|
result = sntContract.methods["approveAndCall"].send(tx, approveAndCallObj, password, success)
|
||||||
if success:
|
if success:
|
||||||
|
@ -288,7 +288,7 @@ proc setPubKeyEstimateGas*(username: string, address: string, pubKey: string, su
|
||||||
except RpcException as e:
|
except RpcException as e:
|
||||||
raise
|
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)
|
var hash = namehash(username)
|
||||||
hash.removePrefix("0x")
|
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)
|
setPubkey = SetPubkey(label: label, x: x, y: y)
|
||||||
resolverAddress = resolver(hash)
|
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:
|
try:
|
||||||
result = resolverContract.methods["setPubkey"].send(tx, setPubkey, password, success)
|
result = resolverContract.methods["setPubkey"].send(tx, setPubkey, password, success)
|
||||||
|
|
|
@ -3,6 +3,8 @@ import
|
||||||
|
|
||||||
import
|
import
|
||||||
web3/[conversions, ethtypes], stint
|
web3/[conversions, ethtypes], stint
|
||||||
|
|
||||||
|
import ../types/transaction
|
||||||
|
|
||||||
# TODO: make this public in nim-web3 lib
|
# TODO: make this public in nim-web3 lib
|
||||||
template stripLeadingZeros*(value: string): string =
|
template stripLeadingZeros*(value: string): string =
|
||||||
|
@ -12,16 +14,20 @@ template stripLeadingZeros*(value: string): string =
|
||||||
cidx.inc
|
cidx.inc
|
||||||
value[cidx .. ^1]
|
value[cidx .. ^1]
|
||||||
|
|
||||||
# TODO: update this in nim-web3
|
proc `%`*(x: TransactionData): JsonNode =
|
||||||
proc `%`*(x: EthSend): JsonNode =
|
|
||||||
result = newJobject()
|
result = newJobject()
|
||||||
result["from"] = %x.source
|
result["from"] = %x.source
|
||||||
|
result["type"] = %x.txType
|
||||||
if x.to.isSome:
|
if x.to.isSome:
|
||||||
result["to"] = %x.to.unsafeGet
|
result["to"] = %x.to.unsafeGet
|
||||||
if x.gas.isSome:
|
if x.gas.isSome:
|
||||||
result["gas"] = %x.gas.unsafeGet
|
result["gas"] = %x.gas.unsafeGet
|
||||||
if x.gasPrice.isSome:
|
if x.gasPrice.isSome:
|
||||||
result["gasPrice"] = %("0x" & x.gasPrice.unsafeGet.toHex.stripLeadingZeros)
|
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:
|
if x.value.isSome:
|
||||||
result["value"] = %("0x" & x.value.unsafeGet.toHex)
|
result["value"] = %("0x" & x.value.unsafeGet.toHex)
|
||||||
result["data"] = %x.data
|
result["data"] = %x.data
|
||||||
|
|
|
@ -2,9 +2,9 @@ import
|
||||||
web3/ethtypes
|
web3/ethtypes
|
||||||
|
|
||||||
import
|
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
|
success = true
|
||||||
try:
|
try:
|
||||||
let response = transactions.sendTransaction(tx, password)
|
let response = transactions.sendTransaction(tx, password)
|
||||||
|
@ -13,7 +13,7 @@ proc sendTransaction*(tx: var EthSend, password: string, success: var bool): str
|
||||||
success = false
|
success = false
|
||||||
result = e.msg
|
result = e.msg
|
||||||
|
|
||||||
proc estimateGas*(tx: var EthSend, success: var bool): string =
|
proc estimateGas*(tx: var TransactionData, success: var bool): string =
|
||||||
success = true
|
success = true
|
||||||
try:
|
try:
|
||||||
let response = transactions.estimateGas(tx)
|
let response = transactions.estimateGas(tx)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import
|
||||||
nimcrypto, web3/[encoding, ethtypes]
|
nimcrypto, web3/[encoding, ethtypes]
|
||||||
|
|
||||||
import
|
import
|
||||||
../../types/[rpc_response], ../coder, eth, transactions
|
../../types/[rpc_response, transaction], ../coder, eth, transactions
|
||||||
|
|
||||||
export sendTransaction
|
export sendTransaction
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ proc encodeAbi*(self: Method, obj: object = RootObj()): string =
|
||||||
result &= encoded.data
|
result &= encoded.data
|
||||||
result &= 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
|
success = true
|
||||||
tx.data = self.encodeAbi(methodDescriptor)
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
try:
|
try:
|
||||||
|
@ -48,11 +48,11 @@ proc estimateGas*(self: Method, tx: var EthSend, methodDescriptor: object, succe
|
||||||
success = false
|
success = false
|
||||||
result = e.msg
|
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)
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
result = eth.sendTransaction(tx, password, success)
|
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
|
success = true
|
||||||
tx.data = self.encodeAbi(methodDescriptor)
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
let response: RpcResponse
|
let response: RpcResponse
|
||||||
|
|
|
@ -5,9 +5,9 @@ import
|
||||||
json_serialization, chronicles, web3/ethtypes
|
json_serialization, chronicles, web3/ethtypes
|
||||||
|
|
||||||
import
|
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])
|
let response = core.callPrivateRPC("eth_estimateGas", %*[%tx])
|
||||||
result = Json.decode(response, RpcResponse)
|
result = Json.decode(response, RpcResponse)
|
||||||
if not result.error.isNil:
|
if not result.error.isNil:
|
||||||
|
@ -15,7 +15,7 @@ proc estimateGas*(tx: EthSend): RpcResponse =
|
||||||
|
|
||||||
trace "Gas estimated succesfully", estimate=result.result
|
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)
|
let responseStr = core.sendTransaction($(%tx), password)
|
||||||
result = Json.decode(responseStr, RpcResponse)
|
result = Json.decode(responseStr, RpcResponse)
|
||||||
if not result.error.isNil:
|
if not result.error.isNil:
|
||||||
|
@ -23,7 +23,7 @@ proc sendTransaction*(tx: EthSend, password: string): RpcResponse =
|
||||||
|
|
||||||
trace "Transaction sent succesfully", hash=result.result
|
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"])
|
let responseStr = core.callPrivateRPC("eth_call", %*[%tx, "latest"])
|
||||||
result = Json.decode(responseStr, RpcResponse)
|
result = Json.decode(responseStr, RpcResponse)
|
||||||
if not result.error.isNil:
|
if not result.error.isNil:
|
||||||
|
|
|
@ -144,4 +144,20 @@ proc fetchCryptoServices*(success: var bool): string =
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
success = false
|
success = false
|
||||||
error "Error getting crypto services: ", msg = e.msg
|
error "Error getting crypto services: ", msg = e.msg
|
||||||
result = ""
|
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)
|
||||||
|
|
|
@ -56,11 +56,13 @@ const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"])
|
||||||
type ProviderModel* = ref object
|
type ProviderModel* = ref object
|
||||||
events*: EventEmitter
|
events*: EventEmitter
|
||||||
permissions*: PermissionsModel
|
permissions*: PermissionsModel
|
||||||
|
wallet*: WalletModel
|
||||||
|
|
||||||
proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel): ProviderModel =
|
proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel, wallet: WalletModel): ProviderModel =
|
||||||
result = ProviderModel()
|
result = ProviderModel()
|
||||||
result.events = events
|
result.events = events
|
||||||
result.permissions = permissions
|
result.permissions = permissions
|
||||||
|
result.wallet = wallet
|
||||||
|
|
||||||
proc requestType(message: string): RequestTypes =
|
proc requestType(message: string): RequestTypes =
|
||||||
let data = message.parseJson
|
let data = message.parseJson
|
||||||
|
@ -114,6 +116,8 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string =
|
||||||
let password = request["password"].getStr()
|
let password = request["password"].getStr()
|
||||||
let selectedGasLimit = request["selectedGasLimit"].getStr()
|
let selectedGasLimit = request["selectedGasLimit"].getStr()
|
||||||
let selectedGasPrice = request["selectedGasPrice"].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):
|
let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull):
|
||||||
request["params"][0]["data"].getStr()
|
request["params"][0]["data"].getStr()
|
||||||
else:
|
else:
|
||||||
|
@ -124,8 +128,10 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string =
|
||||||
var response = ""
|
var response = ""
|
||||||
var validInput: bool = true
|
var validInput: bool = true
|
||||||
|
|
||||||
|
let eip1559Enabled = self.wallet.isEIP1559Enabled()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, "dummy")
|
validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
validInput = false
|
validInput = false
|
||||||
success = false
|
success = false
|
||||||
|
@ -133,7 +139,7 @@ proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string =
|
||||||
|
|
||||||
if validInput:
|
if validInput:
|
||||||
# TODO make this async
|
# 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:
|
errorMessage = if not success:
|
||||||
if response == "":
|
if response == "":
|
||||||
"web3-response-error"
|
"web3-response-error"
|
||||||
|
|
|
@ -8,6 +8,7 @@ type WalletSignal* = ref object of Signal
|
||||||
eventType*: string
|
eventType*: string
|
||||||
blockNumber*: int
|
blockNumber*: int
|
||||||
accounts*: seq[string]
|
accounts*: seq[string]
|
||||||
|
baseFeePerGas*: string
|
||||||
# newTransactions*: ???
|
# newTransactions*: ???
|
||||||
erc20*: bool
|
erc20*: bool
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ proc fromEvent*(T: type WalletSignal, jsonSignal: JsonNode): WalletSignal =
|
||||||
if jsonSignal["event"].kind != JNull:
|
if jsonSignal["event"].kind != JNull:
|
||||||
result.eventType = jsonSignal["event"]["type"].getStr
|
result.eventType = jsonSignal["event"]["type"].getStr
|
||||||
result.blockNumber = jsonSignal["event"]{"blockNumber"}.getInt
|
result.blockNumber = jsonSignal["event"]{"blockNumber"}.getInt
|
||||||
|
result.baseFeePerGas = jsonSignal["event"]{"baseFeePerGas"}.getStr
|
||||||
result.erc20 = jsonSignal["event"]{"erc20"}.getBool
|
result.erc20 = jsonSignal["event"]{"erc20"}.getBool
|
||||||
result.accounts = @[]
|
result.accounts = @[]
|
||||||
if jsonSignal["event"]["accounts"].kind != JNull:
|
if jsonSignal["event"]["accounts"].kind != JNull:
|
||||||
|
|
|
@ -50,7 +50,7 @@ proc newStatusInstance*(fleetConfig: string): Status =
|
||||||
result.mailservers = mailservers.newMailserversModel(result.events)
|
result.mailservers = mailservers.newMailserversModel(result.events)
|
||||||
result.browser = browser.newBrowserModel(result.events)
|
result.browser = browser.newBrowserModel(result.events)
|
||||||
result.tokens = tokens.newTokensModel(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)
|
result.osnotifications = newOsNotifications(result.events)
|
||||||
|
|
||||||
proc initNode*(self: Status, statusGoDir, keystoreDir: string) =
|
proc initNode*(self: Status, statusGoDir, keystoreDir: string) =
|
||||||
|
|
|
@ -43,14 +43,14 @@ proc init*(self: StickersModel) =
|
||||||
var evArgs = StickerArgs(e)
|
var evArgs = StickerArgs(e)
|
||||||
self.addStickerToRecent(evArgs.sticker, evArgs.save)
|
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()
|
sntContract = status_contracts.getSntContract()
|
||||||
let
|
let
|
||||||
stickerMktContract = status_contracts.getContract("sticker-market")
|
stickerMktContract = status_contracts.getContract("sticker-market")
|
||||||
buyToken = BuyToken(packId: packId, address: address, price: price)
|
buyToken = BuyToken(packId: packId, address: address, price: price)
|
||||||
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
|
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
|
||||||
approveAndCall = ApproveAndCall[100](to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
|
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 =
|
proc estimateGas*(packId: int, address: string, price: string, success: var bool): int =
|
||||||
var
|
var
|
||||||
|
@ -68,7 +68,7 @@ proc estimateGas*(packId: int, address: string, price: string, success: var bool
|
||||||
if success:
|
if success:
|
||||||
result = fromHex[int](response)
|
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
|
var
|
||||||
sntContract: Erc20Contract
|
sntContract: Erc20Contract
|
||||||
approveAndCall: ApproveAndCall[100]
|
approveAndCall: ApproveAndCall[100]
|
||||||
|
@ -79,7 +79,10 @@ proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, p
|
||||||
approveAndCall,
|
approveAndCall,
|
||||||
sntContract,
|
sntContract,
|
||||||
gas,
|
gas,
|
||||||
gasPrice
|
gasPrice,
|
||||||
|
isEIP1559Enabled,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
maxFeePerGas
|
||||||
)
|
)
|
||||||
|
|
||||||
result = sntContract.methods["approveAndCall"].send(tx, approveAndCall, password, success)
|
result = sntContract.methods["approveAndCall"].send(tx, approveAndCall, password, success)
|
||||||
|
|
|
@ -2,19 +2,25 @@ import
|
||||||
options, strutils
|
options, strutils
|
||||||
|
|
||||||
import
|
import
|
||||||
stint, web3/ethtypes
|
stint, web3/ethtypes, types/transaction
|
||||||
|
|
||||||
from utils as status_utils import toUInt64, gwei2Wei, parseAddress
|
from utils as status_utils import toUInt64, gwei2Wei, parseAddress
|
||||||
|
|
||||||
proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", data = ""): EthSend =
|
proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionData =
|
||||||
result = EthSend(
|
result = TransactionData(
|
||||||
source: source,
|
source: source,
|
||||||
value: value.some,
|
value: value.some,
|
||||||
gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).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),
|
gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some),
|
||||||
data: data
|
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 =
|
proc buildTokenTransaction*(source, contractAddress: Address, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = ""): TransactionData =
|
||||||
result = buildTransaction(source, 0.u256, gas, gasPrice)
|
result = buildTransaction(source, 0.u256, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas)
|
||||||
result.to = contractAddress.some
|
result.to = contractAddress.some
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import strutils
|
import strutils
|
||||||
|
import web3/ethtypes, options, stint
|
||||||
include pending_transaction_type
|
include pending_transaction_type
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -22,6 +22,19 @@ type
|
||||||
fromAddress*: string
|
fromAddress*: string
|
||||||
to*: 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 =
|
proc cmpTransactions*(x, y: Transaction): int =
|
||||||
# Sort proc to compare transactions from a single account.
|
# Sort proc to compare transactions from a single account.
|
||||||
# Compares first by block number, then by nonce
|
# Compares first by block number, then by nonce
|
||||||
|
|
|
@ -87,7 +87,9 @@ proc wei2Eth*(input: string, decimals: int): string =
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error "Error parsing this wei value", input, msg=e.msg
|
error "Error parsing this wei value", input, msg=e.msg
|
||||||
result = "0"
|
result = "0"
|
||||||
|
|
||||||
|
proc wei2Gwei*(input: string): string =
|
||||||
|
result = wei2Eth(input, 9)
|
||||||
|
|
||||||
proc first*(jArray: JsonNode, fieldName, id: string): JsonNode =
|
proc first*(jArray: JsonNode, fieldName, id: string): JsonNode =
|
||||||
if jArray == nil:
|
if jArray == nil:
|
||||||
|
@ -139,13 +141,19 @@ proc isAddress*(strAddress: string): bool =
|
||||||
return false
|
return false
|
||||||
return true
|
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(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 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 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 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 uuid.isEmptyOrWhitespace(): raise newException(ValueError, "uuid is required")
|
||||||
|
|
||||||
if assetAddress != "": # If a token is being used
|
if assetAddress != "": # If a token is being used
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import json, strformat, strutils, chronicles, sequtils, sugar, httpclient, tables, net
|
import json, strformat, strutils, chronicles, sequtils, sugar, httpclient, tables, net
|
||||||
import json_serialization, stint
|
import json_serialization, stint, stew/byteutils, algorithm
|
||||||
from web3/ethtypes import Address, EthSend, Quantity
|
from web3/ethtypes import Address, Quantity
|
||||||
from web3/conversions import `$`
|
from web3/conversions import `$`
|
||||||
from libstatus/core import getBlockByNumber
|
from libstatus/core import getBlockByNumber
|
||||||
import libstatus/accounts as status_accounts
|
import libstatus/accounts as status_accounts
|
||||||
|
@ -10,7 +10,7 @@ import libstatus/wallet as status_wallet
|
||||||
import libstatus/accounts/constants as constants
|
import libstatus/accounts/constants as constants
|
||||||
import libstatus/eth/[eth, contracts]
|
import libstatus/eth/[eth, contracts]
|
||||||
from libstatus/core import getBlockByNumber
|
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/[balance_manager, collectibles]
|
||||||
import wallet/account as wallet_account
|
import wallet/account as wallet_account
|
||||||
import transactions
|
import transactions
|
||||||
|
@ -38,6 +38,8 @@ type WalletModel* = ref object
|
||||||
defaultCurrency*: string
|
defaultCurrency*: string
|
||||||
tokens*: seq[Erc20Contract]
|
tokens*: seq[Erc20Contract]
|
||||||
totalBalance*: float
|
totalBalance*: float
|
||||||
|
eip1559Enabled*: bool
|
||||||
|
latestBaseFee*: string
|
||||||
|
|
||||||
proc getDefaultCurrency*(self: WalletModel): string
|
proc getDefaultCurrency*(self: WalletModel): string
|
||||||
proc calculateTotalFiatBalance*(self: WalletModel)
|
proc calculateTotalFiatBalance*(self: WalletModel)
|
||||||
|
@ -49,6 +51,7 @@ proc newWalletModel*(events: EventEmitter): WalletModel =
|
||||||
result.events = events
|
result.events = events
|
||||||
result.defaultCurrency = ""
|
result.defaultCurrency = ""
|
||||||
result.totalBalance = 0.0
|
result.totalBalance = 0.0
|
||||||
|
result.eip1559Enabled = false
|
||||||
|
|
||||||
proc initEvents*(self: WalletModel) =
|
proc initEvents*(self: WalletModel) =
|
||||||
self.events.on("currencyChanged") do(e: Args):
|
self.events.on("currencyChanged") do(e: Args):
|
||||||
|
@ -64,12 +67,12 @@ proc initEvents*(self: WalletModel) =
|
||||||
proc delete*(self: WalletModel) =
|
proc delete*(self: WalletModel) =
|
||||||
discard
|
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)
|
contract = getErc20Contract(assetAddress)
|
||||||
if contract == nil:
|
if contract == nil:
|
||||||
raise newException(ValueError, fmt"Could not find ERC-20 contract with address '{assetAddress}' for the current network")
|
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))
|
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 =
|
proc getKnownTokenContract*(self: WalletModel, address: Address): Erc20Contract =
|
||||||
getErc20Contracts().concat(getCustomTokens()).getErc20ContractByAddress(address)
|
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)
|
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()
|
let response = getBlockByNumber("latest").parseJson()
|
||||||
if not response.hasKey("result"):
|
if response.hasKey("result"):
|
||||||
return -1
|
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) =
|
proc checkPendingTransactions*(self: WalletModel, latestBlockNumber: int) =
|
||||||
let latestBlockNumber = self.getLatestBlockNumber()
|
|
||||||
if latestBlockNumber == -1:
|
if latestBlockNumber == -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -133,10 +138,10 @@ proc estimateTokenGas*(self: WalletModel, source, to, assetAddress, value: strin
|
||||||
|
|
||||||
result = contract.methods["transfer"].estimateGas(tx, transfer, success)
|
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(
|
var tx = transactions.buildTransaction(
|
||||||
parseAddress(source),
|
parseAddress(source),
|
||||||
eth2Wei(parseFloat(value), 18), gas, gasPrice, data
|
eth2Wei(parseFloat(value), 18), gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas, data
|
||||||
)
|
)
|
||||||
|
|
||||||
if to != "":
|
if to != "":
|
||||||
|
@ -146,7 +151,7 @@ proc sendTransaction*(source, to, value, gas, gasPrice, password: string, succes
|
||||||
if success:
|
if success:
|
||||||
trackPendingTransaction(result, $source, $to, PendingTransactionType.WalletTransfer, "")
|
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
|
var
|
||||||
transfer: Transfer
|
transfer: Transfer
|
||||||
contract: Erc20Contract
|
contract: Erc20Contract
|
||||||
|
@ -158,7 +163,8 @@ proc sendTokenTransaction*(source, to, assetAddress, value, gas, gasPrice, passw
|
||||||
transfer,
|
transfer,
|
||||||
contract,
|
contract,
|
||||||
gas,
|
gas,
|
||||||
gasPrice
|
gasPrice,
|
||||||
|
isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas
|
||||||
)
|
)
|
||||||
|
|
||||||
result = contract.methods["transfer"].send(tx, transfer, password, success)
|
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())
|
updateBalance(account, self.getDefaultCurrency())
|
||||||
account
|
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) =
|
proc initAccounts*(self: WalletModel) =
|
||||||
self.tokens = status_tokens.getVisibleTokens()
|
self.tokens = status_tokens.getVisibleTokens()
|
||||||
let accounts = status_wallet.getWalletAccounts()
|
let accounts = status_wallet.getWalletAccounts()
|
||||||
|
@ -354,23 +391,6 @@ proc getTransfersByAddress*(self: WalletModel, address: string, toBlock: Uint256
|
||||||
proc validateMnemonic*(self: WalletModel, mnemonic: string): string =
|
proc validateMnemonic*(self: WalletModel, mnemonic: string): string =
|
||||||
result = status_wallet.validateMnemonic(mnemonic).parseJSON()["error"].getStr
|
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 =
|
proc checkRecentHistory*(self: WalletModel, addresses: seq[string]): string =
|
||||||
result = status_wallet.checkRecentHistory(addresses)
|
result = status_wallet.checkRecentHistory(addresses)
|
||||||
|
|
||||||
|
@ -405,4 +425,36 @@ proc getOpenseaCollections*(address: string): string =
|
||||||
result = status_wallet.getOpenseaCollections(address)
|
result = status_wallet.getOpenseaCollections(address)
|
||||||
|
|
||||||
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
|
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
|
||||||
result = status_wallet.getOpenseaAssets(address, collectionSlug, limit)
|
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
|
||||||
|
|
Loading…
Reference in New Issue