mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-05 23:23:08 +00:00
Add EIP-1559 implementation for gas price
This commit is contained in:
parent
bbced46733
commit
fe7a5bc917
410
ethers/contract.nim
Normal file
410
ethers/contract.nim
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
import pkg/serde
|
||||||
|
import std/macros
|
||||||
|
import std/sequtils
|
||||||
|
import pkg/chronicles
|
||||||
|
import pkg/chronos
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/contractabi
|
||||||
|
import ./basics
|
||||||
|
import ./provider
|
||||||
|
import ./signer
|
||||||
|
import ./events
|
||||||
|
import ./errors
|
||||||
|
import ./errors/conversion
|
||||||
|
import ./fields
|
||||||
|
|
||||||
|
export basics
|
||||||
|
export provider
|
||||||
|
export events
|
||||||
|
export errors.SolidityError
|
||||||
|
export errors.errors
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "ethers contract"
|
||||||
|
|
||||||
|
type
|
||||||
|
Contract* = ref object of RootObj
|
||||||
|
provider: Provider
|
||||||
|
signer: ?Signer
|
||||||
|
address: Address
|
||||||
|
TransactionOverrides* = ref object of RootObj
|
||||||
|
nonce*: ?UInt256
|
||||||
|
chainId*: ?UInt256
|
||||||
|
gasPrice*: ?UInt256
|
||||||
|
maxFee*: ?UInt256
|
||||||
|
maxPriorityFee*: ?UInt256
|
||||||
|
maxPriorityFeePerGas*: ?UInt256
|
||||||
|
gasLimit*: ?UInt256
|
||||||
|
CallOverrides* = ref object of TransactionOverrides
|
||||||
|
blockTag*: ?BlockTag
|
||||||
|
Confirmable* = object
|
||||||
|
response*: ?TransactionResponse
|
||||||
|
convert*: ConvertCustomErrors
|
||||||
|
EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
|
||||||
|
|
||||||
|
func new*(ContractType: type Contract,
|
||||||
|
address: Address,
|
||||||
|
provider: Provider): ContractType =
|
||||||
|
ContractType(provider: provider, address: address)
|
||||||
|
|
||||||
|
func new*(ContractType: type Contract,
|
||||||
|
address: Address,
|
||||||
|
signer: Signer): ContractType {.raises: [SignerError].} =
|
||||||
|
ContractType(signer: some signer, provider: signer.provider, address: address)
|
||||||
|
|
||||||
|
func connect*[T: Contract](contract: T, provider: Provider | Signer): T {.raises: [SignerError].} =
|
||||||
|
T.new(contract.address, provider)
|
||||||
|
|
||||||
|
func provider*(contract: Contract): Provider =
|
||||||
|
contract.provider
|
||||||
|
|
||||||
|
func signer*(contract: Contract): ?Signer =
|
||||||
|
contract.signer
|
||||||
|
|
||||||
|
func address*(contract: Contract): Address =
|
||||||
|
contract.address
|
||||||
|
|
||||||
|
template raiseContractError(message: string) =
|
||||||
|
raise newException(ContractError, message)
|
||||||
|
|
||||||
|
proc createTransaction(contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides()): Transaction =
|
||||||
|
let selector = selector(function, typeof parameters).toArray
|
||||||
|
let data = @selector & AbiEncoder.encode(parameters)
|
||||||
|
Transaction(
|
||||||
|
to: contract.address,
|
||||||
|
data: data,
|
||||||
|
nonce: overrides.nonce,
|
||||||
|
chainId: overrides.chainId,
|
||||||
|
gasPrice: overrides.gasPrice,
|
||||||
|
maxFee: overrides.maxFee,
|
||||||
|
maxPriorityFee: overrides.maxPriorityFee,
|
||||||
|
maxPriorityFeePerGas: overrides.maxPriorityFeePerGas,
|
||||||
|
gasLimit: overrides.gasLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
||||||
|
without decoded =? AbiDecoder.decode(bytes, T):
|
||||||
|
raiseContractError "unable to decode return value as " & $T
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
proc call(
|
||||||
|
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
||||||
|
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||||
|
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
||||||
|
await provider.call(transaction, blockTag)
|
||||||
|
else:
|
||||||
|
await provider.call(transaction)
|
||||||
|
|
||||||
|
proc call(
|
||||||
|
contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides(),
|
||||||
|
) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
||||||
|
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
|
||||||
|
if signer =? contract.signer and transaction.sender.isNone:
|
||||||
|
transaction.sender = some(await signer.getAddress())
|
||||||
|
|
||||||
|
discard await contract.provider.call(transaction, overrides)
|
||||||
|
|
||||||
|
proc call(
|
||||||
|
contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
ReturnType: type,
|
||||||
|
overrides = TransactionOverrides(),
|
||||||
|
): Future[ReturnType] {.
|
||||||
|
async: (raises: [ProviderError, SignerError, ContractError, CancelledError])
|
||||||
|
.} =
|
||||||
|
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
|
||||||
|
if signer =? contract.signer and transaction.sender.isNone:
|
||||||
|
transaction.sender = some(await signer.getAddress())
|
||||||
|
|
||||||
|
let response = await contract.provider.call(transaction, overrides)
|
||||||
|
return decodeResponse(ReturnType, response)
|
||||||
|
|
||||||
|
proc send(
|
||||||
|
contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides()
|
||||||
|
): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
|
||||||
|
|
||||||
|
if signer =? contract.signer:
|
||||||
|
withLock(signer):
|
||||||
|
let transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
let populated = await signer.populateTransaction(transaction)
|
||||||
|
trace "sending contract transaction", function, params = $parameters
|
||||||
|
let txResp = await signer.sendTransaction(populated)
|
||||||
|
return txResp.some
|
||||||
|
else:
|
||||||
|
await call(contract, function, parameters, overrides)
|
||||||
|
return TransactionResponse.none
|
||||||
|
|
||||||
|
func getParameterTuple(procedure: NimNode): NimNode =
|
||||||
|
let parameters = procedure[3]
|
||||||
|
var tupl = newNimNode(nnkTupleConstr, parameters)
|
||||||
|
for parameter in parameters[2..^1]:
|
||||||
|
for name in parameter[0..^3]:
|
||||||
|
tupl.add name
|
||||||
|
return tupl
|
||||||
|
|
||||||
|
func getErrorTypes(procedure: NimNode): NimNode =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
var tupl = newNimNode(nnkTupleConstr)
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.kind == nnkExprColonExpr:
|
||||||
|
if pragma[0].eqIdent "errors":
|
||||||
|
pragma[1].expectKind(nnkBracket)
|
||||||
|
for error in pragma[1]:
|
||||||
|
tupl.add error
|
||||||
|
if tupl.len == 0:
|
||||||
|
quote do: tuple[]
|
||||||
|
else:
|
||||||
|
tupl
|
||||||
|
|
||||||
|
func isGetter(procedure: NimNode): bool =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.eqIdent "getter":
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func isConstant(procedure: NimNode): bool =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.eqIdent "view":
|
||||||
|
return true
|
||||||
|
elif pragma.eqIdent "pure":
|
||||||
|
return true
|
||||||
|
elif pragma.eqIdent "getter":
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func isMultipleReturn(returnType: NimNode): bool =
|
||||||
|
(returnType.kind == nnkPar and returnType.len > 1) or
|
||||||
|
(returnType.kind == nnkTupleConstr) or
|
||||||
|
(returnType.kind == nnkTupleTy)
|
||||||
|
|
||||||
|
func addOverrides(procedure: var NimNode) =
|
||||||
|
procedure[3].add(
|
||||||
|
newIdentDefs(
|
||||||
|
ident("overrides"),
|
||||||
|
newEmptyNode(),
|
||||||
|
quote do: TransactionOverrides()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func addContractCall(procedure: var NimNode) =
|
||||||
|
let contract = procedure[3][1][0]
|
||||||
|
let function = $basename(procedure[0])
|
||||||
|
let parameters = getParameterTuple(procedure)
|
||||||
|
let returnType = procedure[3][0]
|
||||||
|
let isGetter = procedure.isGetter
|
||||||
|
|
||||||
|
procedure.addOverrides()
|
||||||
|
let errors = getErrorTypes(procedure)
|
||||||
|
|
||||||
|
func call: NimNode =
|
||||||
|
if returnType.kind == nnkEmpty:
|
||||||
|
quote:
|
||||||
|
await call(`contract`, `function`, `parameters`, overrides)
|
||||||
|
elif returnType.isMultipleReturn or isGetter:
|
||||||
|
quote:
|
||||||
|
return await call(
|
||||||
|
`contract`, `function`, `parameters`, `returnType`, overrides
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
quote:
|
||||||
|
# solidity functions return a tuple, so wrap return type in a tuple
|
||||||
|
let tupl = await call(
|
||||||
|
`contract`, `function`, `parameters`, (`returnType`,), overrides
|
||||||
|
)
|
||||||
|
return tupl[0]
|
||||||
|
|
||||||
|
func send: NimNode =
|
||||||
|
if returnType.kind == nnkEmpty:
|
||||||
|
quote:
|
||||||
|
discard await send(`contract`, `function`, `parameters`, overrides)
|
||||||
|
else:
|
||||||
|
quote:
|
||||||
|
when typeof(result) isnot Confirmable:
|
||||||
|
{.error:
|
||||||
|
"unexpected return type, " &
|
||||||
|
"missing {.view.}, {.pure.} or {.getter.} ?"
|
||||||
|
.}
|
||||||
|
let response = await send(`contract`, `function`, `parameters`, overrides)
|
||||||
|
let convert = customErrorConversion(`errors`)
|
||||||
|
Confirmable(response: response, convert: convert)
|
||||||
|
|
||||||
|
procedure[6] =
|
||||||
|
if procedure.isConstant:
|
||||||
|
call()
|
||||||
|
else:
|
||||||
|
send()
|
||||||
|
|
||||||
|
func addErrorHandling(procedure: var NimNode) =
|
||||||
|
let body = procedure[6]
|
||||||
|
let errors = getErrorTypes(procedure)
|
||||||
|
procedure[6] = quote do:
|
||||||
|
try:
|
||||||
|
`body`
|
||||||
|
except ProviderError as error:
|
||||||
|
if data =? error.data:
|
||||||
|
let convert = customErrorConversion(`errors`)
|
||||||
|
raise convert(error)
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
func addFuture(procedure: var NimNode) =
|
||||||
|
let returntype = procedure[3][0]
|
||||||
|
if returntype.kind != nnkEmpty:
|
||||||
|
procedure[3][0] = quote: Future[`returntype`]
|
||||||
|
|
||||||
|
func addAsyncPragma(procedure: var NimNode) =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
if pragmas.kind == nnkEmpty:
|
||||||
|
procedure[4] = newNimNode(nnkPragma)
|
||||||
|
procedure[4].add nnkExprColonExpr.newTree(
|
||||||
|
newIdentNode("async"),
|
||||||
|
nnkTupleConstr.newTree(
|
||||||
|
nnkExprColonExpr.newTree(
|
||||||
|
newIdentNode("raises"),
|
||||||
|
nnkBracket.newTree(
|
||||||
|
newIdentNode("CancelledError"),
|
||||||
|
newIdentNode("ProviderError"),
|
||||||
|
newIdentNode("EthersError"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped =
|
||||||
|
let parameters = procedure[3]
|
||||||
|
let body = procedure[6]
|
||||||
|
|
||||||
|
parameters.expectMinLen(2) # at least return type and contract instance
|
||||||
|
body.expectKind(nnkEmpty)
|
||||||
|
|
||||||
|
var contractcall = copyNimTree(procedure)
|
||||||
|
contractcall.addContractCall()
|
||||||
|
contractcall.addErrorHandling()
|
||||||
|
contractcall.addFuture()
|
||||||
|
contractcall.addAsyncPragma()
|
||||||
|
contractcall
|
||||||
|
|
||||||
|
template view* {.pragma.}
|
||||||
|
template pure* {.pragma.}
|
||||||
|
template getter* {.pragma.}
|
||||||
|
|
||||||
|
proc subscribe*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
handler: EventHandler[E]):
|
||||||
|
Future[Subscription] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = EventFilter(address: contract.address, topics: @[topic])
|
||||||
|
|
||||||
|
proc logHandler(logResult: ?!Log) {.raises: [].} =
|
||||||
|
without log =? logResult, error:
|
||||||
|
handler(failure(E, error))
|
||||||
|
return
|
||||||
|
|
||||||
|
if event =? E.decode(log.data, log.topics):
|
||||||
|
handler(success(event))
|
||||||
|
|
||||||
|
contract.provider.subscribe(filter, logHandler)
|
||||||
|
|
||||||
|
proc confirm(tx: Confirmable, confirmations, timeout: int):
|
||||||
|
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||||
|
|
||||||
|
without response =? tx.response:
|
||||||
|
raise newException(
|
||||||
|
EthersError,
|
||||||
|
"Transaction hash required. Possibly was a call instead of a send?"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await response.confirm(confirmations, timeout)
|
||||||
|
except ProviderError as error:
|
||||||
|
let convert = tx.convert
|
||||||
|
raise convert(error)
|
||||||
|
|
||||||
|
proc confirm*(tx: Future[Confirmable],
|
||||||
|
confirmations: int = EthersDefaultConfirmations,
|
||||||
|
timeout: int = EthersReceiptTimeoutBlks):
|
||||||
|
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||||
|
## Convenience method that allows confirm to be chained to a contract
|
||||||
|
## transaction, eg:
|
||||||
|
## `await token.connect(signer0)
|
||||||
|
## .mint(accounts[1], 100.u256)
|
||||||
|
## .confirm(3)`
|
||||||
|
try:
|
||||||
|
return await (await tx).confirm(confirmations, timeout)
|
||||||
|
except CancelledError as e:
|
||||||
|
raise e
|
||||||
|
except EthersError as e:
|
||||||
|
raise e
|
||||||
|
except CatchableError as e:
|
||||||
|
raise newException(
|
||||||
|
EthersError,
|
||||||
|
"Error when trying to confirm the contract transaction: " & e.msg
|
||||||
|
)
|
||||||
|
|
||||||
|
proc queryFilter[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
filter: EventFilter):
|
||||||
|
Future[seq[E]] {.async.} =
|
||||||
|
|
||||||
|
var logs = await contract.provider.getLogs(filter)
|
||||||
|
logs.keepItIf(not it.removed)
|
||||||
|
|
||||||
|
var events: seq[E] = @[]
|
||||||
|
for log in logs:
|
||||||
|
if event =? E.decode(log.data, log.topics):
|
||||||
|
events.add event
|
||||||
|
|
||||||
|
return events
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = EventFilter(address: contract.address,
|
||||||
|
topics: @[topic])
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
blockHash: BlockHash):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = FilterByBlockHash(address: contract.address,
|
||||||
|
topics: @[topic],
|
||||||
|
blockHash: blockHash)
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
fromBlock: BlockTag,
|
||||||
|
toBlock: BlockTag):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = Filter(address: contract.address,
|
||||||
|
topics: @[topic],
|
||||||
|
fromBlock: fromBlock,
|
||||||
|
toBlock: toBlock)
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
@ -63,6 +63,7 @@ type
|
|||||||
number*: ?UInt256
|
number*: ?UInt256
|
||||||
timestamp*: UInt256
|
timestamp*: UInt256
|
||||||
hash*: ?BlockHash
|
hash*: ?BlockHash
|
||||||
|
baseFeePerGas* : ?UInt256
|
||||||
PastTransaction* {.serialize.} = object
|
PastTransaction* {.serialize.} = object
|
||||||
blockHash*: BlockHash
|
blockHash*: BlockHash
|
||||||
blockNumber*: UInt256
|
blockNumber*: UInt256
|
||||||
@ -121,6 +122,11 @@ method getGasPrice*(
|
|||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
method getMaxPriorityFeePerGas*(
|
||||||
|
provider: Provider
|
||||||
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||||
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: Provider, address: Address, blockTag = BlockTag.latest
|
provider: Provider, address: Address, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||||
|
|||||||
@ -28,6 +28,7 @@ type
|
|||||||
JsonRpcProvider* = ref object of Provider
|
JsonRpcProvider* = ref object of Provider
|
||||||
client: Future[RpcClient]
|
client: Future[RpcClient]
|
||||||
subscriptions: Future[JsonRpcSubscriptions]
|
subscriptions: Future[JsonRpcSubscriptions]
|
||||||
|
maxPriorityFeePerGas: UInt256
|
||||||
|
|
||||||
JsonRpcSubscription* = ref object of Subscription
|
JsonRpcSubscription* = ref object of Subscription
|
||||||
subscriptions: JsonRpcSubscriptions
|
subscriptions: JsonRpcSubscriptions
|
||||||
@ -43,6 +44,7 @@ type
|
|||||||
|
|
||||||
const defaultUrl = "http://localhost:8545"
|
const defaultUrl = "http://localhost:8545"
|
||||||
const defaultPollingInterval = 4.seconds
|
const defaultPollingInterval = 4.seconds
|
||||||
|
const defaultMaxPriorityFeePerGas = 1_000_000_000.u256
|
||||||
|
|
||||||
proc jsonHeaders: seq[(string, string)] =
|
proc jsonHeaders: seq[(string, string)] =
|
||||||
@[("Content-Type", "application/json")]
|
@[("Content-Type", "application/json")]
|
||||||
@ -50,7 +52,8 @@ proc jsonHeaders: seq[(string, string)] =
|
|||||||
proc new*(
|
proc new*(
|
||||||
_: type JsonRpcProvider,
|
_: type JsonRpcProvider,
|
||||||
url=defaultUrl,
|
url=defaultUrl,
|
||||||
pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
pollingInterval=defaultPollingInterval,
|
||||||
|
maxPriorityFeePerGas=defaultMaxPriorityFeePerGas): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
||||||
|
|
||||||
var initialized: Future[void]
|
var initialized: Future[void]
|
||||||
var client: RpcClient
|
var client: RpcClient
|
||||||
@ -87,7 +90,7 @@ proc new*(
|
|||||||
return subscriptions
|
return subscriptions
|
||||||
|
|
||||||
initialized = initialize()
|
initialized = initialize()
|
||||||
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions(), maxPriorityFeePerGas: maxPriorityFeePerGas)
|
||||||
|
|
||||||
proc callImpl(
|
proc callImpl(
|
||||||
client: RpcClient, call: string, args: JsonNode
|
client: RpcClient, call: string, args: JsonNode
|
||||||
@ -151,6 +154,12 @@ method getGasPrice*(
|
|||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_gasPrice()
|
return await client.eth_gasPrice()
|
||||||
|
|
||||||
|
method getMaxPriorityFeePerGas*(
|
||||||
|
provider: JsonRpcProvider
|
||||||
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||||
|
convertError:
|
||||||
|
return provider.maxPriorityFeePerGas
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
import pkg/chronicles
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./errors
|
import ./errors
|
||||||
import ./provider
|
import ./provider
|
||||||
@ -55,6 +56,11 @@ method getGasPrice*(
|
|||||||
.} =
|
.} =
|
||||||
return await signer.provider.getGasPrice()
|
return await signer.provider.getGasPrice()
|
||||||
|
|
||||||
|
method getMaxPriorityFeePerGas*(
|
||||||
|
signer: Signer
|
||||||
|
): Future[UInt256] {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
||||||
|
return await signer.provider.getMaxPriorityFeePerGas()
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
signer: Signer, blockTag = BlockTag.latest
|
signer: Signer, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
@ -124,8 +130,22 @@ method populateTransaction*(
|
|||||||
populated.sender = some(address)
|
populated.sender = some(address)
|
||||||
if transaction.chainId.isNone:
|
if transaction.chainId.isNone:
|
||||||
populated.chainId = some(await signer.getChainId())
|
populated.chainId = some(await signer.getChainId())
|
||||||
if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone):
|
|
||||||
populated.gasPrice = some(await signer.getGasPrice())
|
let blk = await signer.provider.getBlock(BlockTag.latest)
|
||||||
|
|
||||||
|
if baseFeePerGas =? blk.?baseFeePerGas:
|
||||||
|
trace "EIP-1559 is supported"
|
||||||
|
|
||||||
|
let maxPriorityFeePerGas = transaction.maxPriorityFeePerGas |? (await signer.provider.getMaxPriorityFeePerGas())
|
||||||
|
populated.maxPriorityFeePerGas = some(maxPriorityFeePerGas)
|
||||||
|
|
||||||
|
# Multiply by 2 because during times of congestion, it can increase by 12.5% per block.
|
||||||
|
# https://github.com/ethers-io/ethers.js/discussions/3601#discussioncomment-4461273
|
||||||
|
let maxFeePerGas = baseFeePerGas * 2 + maxPriorityFeePerGas
|
||||||
|
populated.maxFeePerGas = some(maxFeePerGas)
|
||||||
|
else:
|
||||||
|
trace "EIP-1559 is not supported"
|
||||||
|
populated.gasPrice = some(transaction.gasPrice |? (await signer.getGasPrice()))
|
||||||
|
|
||||||
if transaction.nonce.isNone and transaction.gasLimit.isNone:
|
if transaction.nonce.isNone and transaction.gasLimit.isNone:
|
||||||
# when both nonce and gasLimit are not populated, we must ensure getNonce is
|
# when both nonce and gasLimit are not populated, we must ensure getNonce is
|
||||||
|
|||||||
@ -17,6 +17,8 @@ type
|
|||||||
gasPrice*: ?UInt256
|
gasPrice*: ?UInt256
|
||||||
maxFee*: ?UInt256
|
maxFee*: ?UInt256
|
||||||
maxPriorityFee*: ?UInt256
|
maxPriorityFee*: ?UInt256
|
||||||
|
maxPriorityFeePerGas*: ?UInt256
|
||||||
|
maxFeePerGas*: ?UInt256
|
||||||
gasLimit*: ?UInt256
|
gasLimit*: ?UInt256
|
||||||
transactionType* {.serialize("type").}: ?TransactionType
|
transactionType* {.serialize("type").}: ?TransactionType
|
||||||
|
|
||||||
|
|||||||
@ -55,20 +55,27 @@ suite "JsonRpcSigner":
|
|||||||
let transaction = Transaction.example
|
let transaction = Transaction.example
|
||||||
let populated = await signer.populateTransaction(transaction)
|
let populated = await signer.populateTransaction(transaction)
|
||||||
check !populated.sender == await signer.getAddress()
|
check !populated.sender == await signer.getAddress()
|
||||||
check !populated.gasPrice == await signer.getGasPrice()
|
|
||||||
check !populated.nonce == await signer.getTransactionCount(BlockTag.pending)
|
check !populated.nonce == await signer.getTransactionCount(BlockTag.pending)
|
||||||
check !populated.gasLimit == await signer.estimateGas(transaction)
|
check !populated.gasLimit == await signer.estimateGas(transaction)
|
||||||
check !populated.chainId == await signer.getChainId()
|
check !populated.chainId == await signer.getChainId()
|
||||||
|
|
||||||
|
let blk = !(await signer.provider.getBlock(BlockTag.latest))
|
||||||
|
check !populated.maxPriorityFeePerGas == await signer.getMaxPriorityFeePerGas()
|
||||||
|
check !populated.maxFeePerGas == !blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas
|
||||||
|
|
||||||
test "populate does not overwrite existing fields":
|
test "populate does not overwrite existing fields":
|
||||||
let signer = provider.getSigner()
|
let signer = provider.getSigner()
|
||||||
var transaction = Transaction.example
|
var transaction = Transaction.example
|
||||||
transaction.sender = some await signer.getAddress()
|
transaction.sender = some await signer.getAddress()
|
||||||
transaction.nonce = some UInt256.example
|
transaction.nonce = some UInt256.example
|
||||||
transaction.chainId = some await signer.getChainId()
|
transaction.chainId = some await signer.getChainId()
|
||||||
transaction.gasPrice = some UInt256.example
|
transaction.maxPriorityFeePerGas = some UInt256.example
|
||||||
transaction.gasLimit = some UInt256.example
|
transaction.gasLimit = some UInt256.example
|
||||||
let populated = await signer.populateTransaction(transaction)
|
let populated = await signer.populateTransaction(transaction)
|
||||||
|
|
||||||
|
let blk = !(await signer.provider.getBlock(BlockTag.latest))
|
||||||
|
transaction.maxFeePerGas = some(!blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas)
|
||||||
|
|
||||||
check populated == transaction
|
check populated == transaction
|
||||||
|
|
||||||
test "populate fails when sender does not match signer address":
|
test "populate fails when sender does not match signer address":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user