mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-15 12:03:08 +00:00
131 lines
4.4 KiB
Nim
131 lines
4.4 KiB
Nim
import ./basics
|
|
import ./provider
|
|
import pkg/chronicles
|
|
|
|
export basics
|
|
export chronicles
|
|
|
|
logScope:
|
|
topics = "ethers signer"
|
|
|
|
type
|
|
Signer* = ref object of RootObj
|
|
lastSeenNonce: ?UInt256
|
|
|
|
type SignerError* = object of EthersError
|
|
|
|
template raiseSignerError(message: string, parent: ref ProviderError = nil) =
|
|
raise newException(SignerError, message, parent)
|
|
|
|
method provider*(signer: Signer): Provider {.base, gcsafe.} =
|
|
doAssert false, "not implemented"
|
|
|
|
method getAddress*(signer: Signer): Future[Address] {.base, gcsafe.} =
|
|
doAssert false, "not implemented"
|
|
|
|
method signMessage*(signer: Signer,
|
|
message: seq[byte]): Future[seq[byte]] {.base, async.} =
|
|
doAssert false, "not implemented"
|
|
|
|
method sendTransaction*(signer: Signer,
|
|
transaction: Transaction): Future[TransactionResponse] {.base, async.} =
|
|
doAssert false, "not implemented"
|
|
|
|
method getGasPrice*(signer: Signer): Future[UInt256] {.base, gcsafe.} =
|
|
signer.provider.getGasPrice()
|
|
|
|
method getTransactionCount*(signer: Signer,
|
|
blockTag = BlockTag.latest):
|
|
Future[UInt256] {.base, async.} =
|
|
let address = await signer.getAddress()
|
|
return await signer.provider.getTransactionCount(address, blockTag)
|
|
|
|
method estimateGas*(signer: Signer,
|
|
transaction: Transaction): Future[UInt256] {.base, async.} =
|
|
var transaction = transaction
|
|
transaction.sender = some(await signer.getAddress)
|
|
return await signer.provider.estimateGas(transaction)
|
|
|
|
method getChainId*(signer: Signer): Future[UInt256] {.base, gcsafe.} =
|
|
signer.provider.getChainId()
|
|
|
|
method getNonce(signer: Signer): Future[UInt256] {.base, gcsafe, async.} =
|
|
var nonce = await signer.getTransactionCount(BlockTag.pending)
|
|
|
|
if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce:
|
|
nonce = (lastSeen + 1.u256)
|
|
signer.lastSeenNonce = some nonce
|
|
|
|
return nonce
|
|
|
|
method updateNonce*(signer: Signer, nonce: ?UInt256) {.base, gcsafe.} =
|
|
without nonce =? nonce:
|
|
return
|
|
|
|
without lastSeen =? signer.lastSeenNonce:
|
|
signer.lastSeenNonce = some nonce
|
|
return
|
|
|
|
if nonce > lastSeen:
|
|
signer.lastSeenNonce = some nonce
|
|
|
|
method cancelTransaction(
|
|
signer: Signer,
|
|
tx: Transaction
|
|
): Future[TransactionResponse] {.async, base.} =
|
|
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
|
# with the failed tx's nonce
|
|
|
|
without sender =? tx.sender:
|
|
raiseSignerError "transaction must have sender"
|
|
if sender != await signer.getAddress():
|
|
raiseSignerError "can only cancel a tx this signer has sent"
|
|
without nonce =? tx.nonce:
|
|
raiseSignerError "transaction must have nonce"
|
|
|
|
var cancelTx = tx
|
|
cancelTx.to = sender
|
|
cancelTx.value = 0.u256
|
|
cancelTx.nonce = some nonce
|
|
try:
|
|
cancelTx.gasLimit = some(await signer.estimateGas(cancelTx))
|
|
except ProviderError:
|
|
warn "failed to estimate gas for cancellation tx, sending anyway",
|
|
tx = $cancelTx
|
|
discard
|
|
|
|
trace "cancelling transaction to prevent stuck transactions", nonce
|
|
return await signer.sendTransaction(cancelTx)
|
|
|
|
method populateTransaction*(signer: Signer,
|
|
transaction: Transaction,
|
|
cancelOnEstimateGasError = false):
|
|
Future[Transaction] {.base, async.} =
|
|
|
|
if sender =? transaction.sender and sender != await signer.getAddress():
|
|
raiseSignerError("from address mismatch")
|
|
if chainId =? transaction.chainId and chainId != await signer.getChainId():
|
|
raiseSignerError("chain id mismatch")
|
|
|
|
var populated = transaction
|
|
|
|
if transaction.sender.isNone:
|
|
populated.sender = some(await signer.getAddress())
|
|
if transaction.chainId.isNone:
|
|
populated.chainId = some(await signer.getChainId())
|
|
if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone):
|
|
populated.gasPrice = some(await signer.getGasPrice())
|
|
if transaction.nonce.isNone:
|
|
populated.nonce = some(await signer.getNonce())
|
|
if transaction.gasLimit.isNone:
|
|
try:
|
|
populated.gasLimit = some(await signer.estimateGas(populated))
|
|
except ProviderError as e:
|
|
# send a 0-valued transaction with the errored nonce to prevent stuck txs
|
|
discard await signer.cancelTransaction(populated)
|
|
raiseSignerError "Estimate gas failed -- A cancellation transaction " &
|
|
"has been sent to prevent stuck transactions. See parent exception " &
|
|
"for revert reason.", e
|
|
|
|
return populated
|