Split contract module into several parts

This commit is contained in:
Mark Spanbroek 2025-04-08 17:19:25 +02:00 committed by markspanbroek
parent 51aa7bc1b3
commit def12bfdc1
14 changed files with 471 additions and 415 deletions

View File

@ -1,11 +1,11 @@
import ./ethers/provider import ./ethers/provider
import ./ethers/signer import ./ethers/signer
import ./ethers/providers/jsonrpc import ./ethers/providers/jsonrpc
import ./ethers/contract import ./ethers/contracts
import ./ethers/wallet import ./ethers/wallet
export provider export provider
export signer export signer
export jsonrpc export jsonrpc
export contract export contracts
export wallet export wallet

View File

@ -1,408 +0,0 @@
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
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,
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)

26
ethers/contracts.nim Normal file
View File

@ -0,0 +1,26 @@
import std/macros
import ./contracts/contract
import ./contracts/overrides
import ./contracts/confirmation
import ./contracts/events
import ./contracts/filters
import ./contracts/syntax
import ./contracts/function
export contract
export overrides
export confirmation
export events
export filters
export syntax.view
export syntax.pure
export syntax.getter
export syntax.errors
{.push raises: [].}
macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped =
procedure.params.expectMinLen(2) # at least return type and contract instance
procedure.body.expectKind(nnkEmpty)
createContractFunction(procedure)

View File

@ -0,0 +1,45 @@
import ../provider
import ../errors/conversion
{.push raises: [].}
type Confirmable* = object
response*: ?TransactionResponse
convert*: ConvertCustomErrors
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
)

View File

@ -0,0 +1,36 @@
import ../basics
import ../provider
import ../signer
{.push raises:[].}
type Contract* = ref object of RootObj
provider: Provider
signer: ?Signer
address: Address
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*[C: Contract](contract: C, provider: Provider): C =
C.new(contract.address, provider)
func connect*[C: Contract](contract: C, signer: Signer): C {.raises: [SignerError].} =
C.new(contract.address, signer)
func provider*(contract: Contract): Provider =
contract.provider
func signer*(contract: Contract): ?Signer =
contract.signer
func address*(contract: Contract): Address =
contract.address

View File

@ -0,0 +1,35 @@
import ../basics
import ./contract
import ./overrides
type ContractCall*[Arguments: tuple] = object
contract: Contract
function: string
arguments: Arguments
overrides: TransactionOverrides
func init*[Arguments: tuple](
_: type ContractCall,
contract: Contract,
function: string,
arguments: Arguments,
overrides: TransactionOverrides
): ContractCall[arguments] =
ContractCall[Arguments](
contract: contract,
function: function,
arguments: arguments,
overrides: overrides
)
func contract*(call: ContractCall): Contract =
call.contract
func function*(call: ContractCall): string =
call.function
func arguments*(call: ContractCall): auto =
call.arguments
func overrides*(call: ContractCall): TransactionOverrides =
call.overrides

View File

@ -1,7 +1,7 @@
import std/macros import std/macros
import pkg/contractabi import pkg/contractabi
import ./basics import ../basics
import ./provider import ../provider
type type
Event* = object of RootObj Event* = object of RootObj

View File

@ -0,0 +1,78 @@
import std/sequtils
import pkg/contractabi
import ../basics
import ../provider
import ./contract
import ./events
import ./fields
type EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
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 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)

View File

@ -0,0 +1,86 @@
import std/macros
import ../errors/conversion
import ./syntax
import ./transactions
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 addContractCall(procedure: var NimNode) =
let contractCall = getContractCall(procedure)
let returnType = procedure.params[0]
let isGetter = procedure.isGetter
let errors = getErrorTypes(procedure)
func call: NimNode =
if returnType.kind == nnkEmpty:
quote:
await callTransaction(`contractCall`)
elif returnType.isMultipleReturn or isGetter:
quote:
return await callTransaction(`contractCall`, `returnType`)
else:
quote:
# solidity functions return a tuple, so wrap return type in a tuple
let tupl = await callTransaction(`contractCall`, (`returnType`,))
return tupl[0]
func send: NimNode =
if returnType.kind == nnkEmpty:
quote:
discard await sendTransaction(`contractCall`)
else:
quote:
when typeof(result) isnot Confirmable:
{.error:
"unexpected return type, " &
"missing {.view.}, {.pure.} or {.getter.} ?"
.}
let response = await sendTransaction(`contractCall`)
let convert = customErrorConversion(`errors`)
Confirmable(response: response, convert: convert)
procedure.body =
if procedure.isConstant:
call()
else:
send()
func addErrorHandling(procedure: var NimNode) =
let body = procedure[6]
let errors = getErrorTypes(procedure)
procedure.body = 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 createContractFunction*(procedure: NimNode): NimNode =
result = copyNimTree(procedure)
result.addOverridesParameter()
result.addContractCall()
result.addErrorHandling()
result.addFuture()
result.addAsyncPragma()

View File

@ -0,0 +1,13 @@
import ../basics
import ../blocktag
type
TransactionOverrides* = ref object of RootObj
nonce*: ?UInt256
chainId*: ?UInt256
gasPrice*: ?UInt256
maxFee*: ?UInt256
maxPriorityFee*: ?UInt256
gasLimit*: ?UInt256
CallOverrides* = ref object of TransactionOverrides
blockTag*: ?BlockTag

View File

@ -0,0 +1,76 @@
import std/macros
import ./contractcall
template view* {.pragma.}
template pure* {.pragma.}
template getter* {.pragma.}
template errors*(types) {.pragma.}
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 getContract(procedure: NimNode): NimNode =
let firstArgument = procedure.params[1][0]
quote do:
Contract(`firstArgument`)
func getFunctionName(procedure: NimNode): string =
$basename(procedure[0])
func getArgumentTuple(procedure: NimNode): NimNode =
let parameters = procedure.params
var arguments = newNimNode(nnkTupleConstr, parameters)
for parameter in parameters[2..^2]:
for name in parameter[0..^3]:
arguments.add name
return arguments
func getOverrides(procedure: NimNode): NimNode =
procedure.params.last[^3]
func getContractCall*(procedure: NimNode): NimNode =
let contract = getContract(procedure)
let function = getFunctionName(procedure)
let arguments = getArgumentTuple(procedure)
let overrides = getOverrides(procedure)
quote do:
ContractCall.init(`contract`, `function`, `arguments`, `overrides`)
func addOverridesParameter*(procedure: var NimNode) =
let overrides = genSym(nskParam, "overrides")
procedure.params.add(
newIdentDefs(
overrides,
newEmptyNode(),
quote do: TransactionOverrides()
)
)
func addAsyncPragma*(procedure: var NimNode) =
procedure.addPragma nnkExprColonExpr.newTree(
quote do: async,
quote do: (raises: [CancelledError, ProviderError, EthersError])
)
func addUsedPragma*(procedure: var NimNode) =
procedure.addPragma(quote do: used)

View File

@ -0,0 +1,71 @@
import pkg/contractabi
import pkg/chronicles
import ../basics
import ../provider
import ../signer
import ../transaction
import ./contract
import ./contractcall
import ./overrides
{.push raises: [].}
logScope:
topics = "ethers contract"
proc createTransaction*(call: ContractCall): Transaction =
let selector = selector(call.function, typeof call.arguments).toArray
let data = @selector & AbiEncoder.encode(call.arguments)
Transaction(
to: call.contract.address,
data: data,
nonce: call.overrides.nonce,
chainId: call.overrides.chainId,
gasPrice: call.overrides.gasPrice,
maxFee: call.overrides.maxFee,
maxPriorityFee: call.overrides.maxPriorityFee,
gasLimit: call.overrides.gasLimit,
)
proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
without decoded =? AbiDecoder.decode(bytes, T):
raise newException(ContractError, "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 callTransaction*(call: ContractCall) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
var transaction = createTransaction(call)
if signer =? call.contract.signer and transaction.sender.isNone:
transaction.sender = some(await signer.getAddress())
discard await call.contract.provider.call(transaction, call.overrides)
proc callTransaction*(call: ContractCall, ReturnType: type): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError, CancelledError]).} =
var transaction = createTransaction(call)
if signer =? call.contract.signer and transaction.sender.isNone:
transaction.sender = some(await signer.getAddress())
let response = await call.contract.provider.call(transaction, call.overrides)
return decodeResponse(ReturnType, response)
proc sendTransaction*(call: ContractCall): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
if signer =? call.contract.signer:
withLock(signer):
let transaction = createTransaction(call)
let populated = await signer.populateTransaction(transaction)
trace "sending contract transaction", function = call.function, params = $call.arguments
let txResp = await signer.sendTransaction(populated)
return txResp.some
else:
await callTransaction(call)
return TransactionResponse.none

View File

@ -16,5 +16,3 @@ proc toErr*[E1: ref CatchableError, E2: EthersError](
msg: string = e1.msg): ref E2 = msg: string = e1.msg): ref E2 =
return newException(E2, msg, e1) return newException(E2, msg, e1)
template errors*(types) {.pragma.}