Upgrade to `nim-json-rpc` v0.4.2 and chronos v4 (#64)
* Add json de/serialization lib from codex to handle conversions json-rpc now requires nim-json-serialization to convert types to/from json. Use the nim-json-serialization signatures to call the json serialization lib from nim-codex (should be moved to its own lib) * Add ethers implementation for setMethodHandler Was removed in json-rpc * More json conversion updates * Fix json_rpc.call returning JsonString instead of JsonNode * Update exceptions Use {.async: (raises: [...].} where needed Annotate provider with {.push raises:[].} Format signatures * Start fixing tests (mainly conversion fixes) * rename sender to `from`, update json error logging, add more conversions * Refactor exceptions for providers and signers, fix more tests - signer procs raise SignerError, provider procs raise ProviderError - WalletError now inherits from SignerError - move wallet module under signers - create jsonrpo moudle under signers - bump nim-json-rpc for null-handling fixes - All jsonrpc provider tests passing, still need to fix others * remove raises from async annotation for dynamic dispatch - removes async: raises from getAddress and signTransaction because derived JsonRpcSigner methods were not being used when dynamically dispatched. Once `raises` was removed from the async annotation, the dynamic dispatch worked again. This is only the case for getAddress and signTransaction. - add gcsafe annotation to wallet.provider so that it matches the base method * Catch EstimateGasError before ProviderError EstimateGasError is now a ProviderError (it is a SignerError, and SignerError is a ProviderError), so EstimateGasErrors were not being caught * clean up - all tests passing * support nim 2.0 * lock in chronos version * Add serde options to the json util, along with tests next step is to: 1. change back any ethers var names that were changed for serialization purposes, eg `from` and `type` 2. move the json util to its own lib * bump json-rpc to 0.4.0 and fix test * fix: specify raises for getAddress and sendTransaction Fixes issue where getAddress and sendTransaction could not be found for MockSigner in tests. The problem was that the async: raises update had not been applied to the MockSigner. * handle exceptions during jsonrpc init There are too many exceptions to catch individually, including chronos raising CatchableError exceptions in await expansion. There are also many other errors captured inside of the new proc with CatchableError. Instead of making it more complicated and harder to read, I think sticking with excepting CatchableError inside of convertError is a sensible solution * cleanup * deserialize key defaults to serialize key * Add more tests for OptIn/OptOut/Strict modes, fix logic * use nim-serde instead of json util Allows aliasing of de/serialized fields, so revert changes of sender to `from` and transactionType to `type` * Move hash* shim to its own module * address PR feedback - add comments to hashes shim - remove .catch from callback condition - derive SignerError from EthersError instead of ProviderError. This allows Providers and Signers to be separate, as Ledger does it, to isolate functionality. Some signer functions now raise both ProviderError and SignerError - Update reverts to check for SignerError - Update ERC-20 method comment * rename subscriptions.init > subscriptions.start
This commit is contained in:
parent
fd16d71ea5
commit
43500c63d7
|
@ -5,10 +5,11 @@ license = "MIT"
|
||||||
|
|
||||||
requires "nim >= 1.6.0"
|
requires "nim >= 1.6.0"
|
||||||
requires "chronicles >= 0.10.3 & < 0.11.0"
|
requires "chronicles >= 0.10.3 & < 0.11.0"
|
||||||
requires "chronos >= 3.0.0 & < 4.0.0"
|
requires "chronos >= 4.0.0 & < 4.1.0"
|
||||||
requires "contractabi >= 0.6.0 & < 0.7.0"
|
requires "contractabi >= 0.6.0 & < 0.7.0"
|
||||||
requires "questionable >= 0.10.2 & < 0.11.0"
|
requires "questionable >= 0.10.2 & < 0.11.0"
|
||||||
requires "json_rpc"
|
requires "json_rpc >= 0.4.0 & < 0.5.0"
|
||||||
|
requires "serde >= 0.1.1 & < 0.2.0"
|
||||||
requires "stint"
|
requires "stint"
|
||||||
requires "stew"
|
requires "stew"
|
||||||
requires "eth"
|
requires "eth"
|
||||||
|
|
|
@ -76,5 +76,5 @@ method transferFrom*(token: Erc20Token,
|
||||||
spender: Address,
|
spender: Address,
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
amount: UInt256): ?TransactionResponse {.base, contract.}
|
amount: UInt256): ?TransactionResponse {.base, contract.}
|
||||||
## Moves `amount` tokens from `from` to `to` using the allowance
|
## Moves `amount` tokens from `spender` to `recipient` using the allowance
|
||||||
## mechanism. `amount` is then deducted from the caller's allowance.
|
## mechanism. `amount` is then deducted from the caller's allowance.
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
## Fixes an underlying Exception caused by missing forward declarations for
|
||||||
|
## `std/json.JsonNode.hash`, eg when using `JsonNode` as a `Table` key. Adds
|
||||||
|
## {.raises: [].} for proper exception tracking. Copied from the std/json module
|
||||||
|
|
||||||
|
import std/json
|
||||||
|
import std/hashes
|
||||||
|
|
||||||
|
{.push raises:[].}
|
||||||
|
|
||||||
|
when (NimMajor) >= 2:
|
||||||
|
proc hash*[A](x: openArray[A]): Hash =
|
||||||
|
## Efficient hashing of arrays and sequences.
|
||||||
|
## There must be a `hash` proc defined for the element type `A`.
|
||||||
|
when A is byte:
|
||||||
|
result = murmurHash(x)
|
||||||
|
elif A is char:
|
||||||
|
when nimvm:
|
||||||
|
result = hashVmImplChar(x, 0, x.high)
|
||||||
|
else:
|
||||||
|
result = murmurHash(toOpenArrayByte(x, 0, x.high))
|
||||||
|
else:
|
||||||
|
for a in x:
|
||||||
|
result = result !& hash(a)
|
||||||
|
result = !$result
|
||||||
|
|
||||||
|
func hash*(n: OrderedTable[string, JsonNode]): Hash
|
||||||
|
|
||||||
|
func hash*(n: JsonNode): Hash =
|
||||||
|
## Compute the hash for a JSON node
|
||||||
|
case n.kind
|
||||||
|
of JArray:
|
||||||
|
result = hash(n.elems)
|
||||||
|
of JObject:
|
||||||
|
result = hash(n.fields)
|
||||||
|
of JInt:
|
||||||
|
result = hash(n.num)
|
||||||
|
of JFloat:
|
||||||
|
result = hash(n.fnum)
|
||||||
|
of JBool:
|
||||||
|
result = hash(n.bval.int)
|
||||||
|
of JString:
|
||||||
|
result = hash(n.str)
|
||||||
|
of JNull:
|
||||||
|
result = Hash(0)
|
||||||
|
|
||||||
|
func hash*(n: OrderedTable[string, JsonNode]): Hash =
|
||||||
|
for key, val in n:
|
||||||
|
result = result xor (hash(key) !& hash(val))
|
||||||
|
result = !$result
|
|
@ -1,4 +1,5 @@
|
||||||
import pkg/chronicles
|
import pkg/chronicles
|
||||||
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./transaction
|
import ./transaction
|
||||||
|
@ -14,15 +15,15 @@ type
|
||||||
Provider* = ref object of RootObj
|
Provider* = ref object of RootObj
|
||||||
ProviderError* = object of EthersError
|
ProviderError* = object of EthersError
|
||||||
Subscription* = ref object of RootObj
|
Subscription* = ref object of RootObj
|
||||||
EventFilter* = ref object of RootObj
|
EventFilter* {.serialize.} = ref object of RootObj
|
||||||
address*: Address
|
address*: Address
|
||||||
topics*: seq[Topic]
|
topics*: seq[Topic]
|
||||||
Filter* = ref object of EventFilter
|
Filter* {.serialize.} = ref object of EventFilter
|
||||||
fromBlock*: BlockTag
|
fromBlock*: BlockTag
|
||||||
toBlock*: BlockTag
|
toBlock*: BlockTag
|
||||||
FilterByBlockHash* = ref object of EventFilter
|
FilterByBlockHash* {.serialize.} = ref object of EventFilter
|
||||||
blockHash*: BlockHash
|
blockHash*: BlockHash
|
||||||
Log* = object
|
Log* {.serialize.} = object
|
||||||
blockNumber*: UInt256
|
blockNumber*: UInt256
|
||||||
data*: seq[byte]
|
data*: seq[byte]
|
||||||
logIndex*: UInt256
|
logIndex*: UInt256
|
||||||
|
@ -36,9 +37,9 @@ type
|
||||||
Invalid = 2
|
Invalid = 2
|
||||||
TransactionResponse* = object
|
TransactionResponse* = object
|
||||||
provider*: Provider
|
provider*: Provider
|
||||||
hash*: TransactionHash
|
hash* {.serialize.}: TransactionHash
|
||||||
TransactionReceipt* = object
|
TransactionReceipt* {.serialize.} = object
|
||||||
sender*: ?Address
|
sender* {.serialize("from"), deserialize("from").}: ?Address
|
||||||
to*: ?Address
|
to*: ?Address
|
||||||
contractAddress*: ?Address
|
contractAddress*: ?Address
|
||||||
transactionIndex*: UInt256
|
transactionIndex*: UInt256
|
||||||
|
@ -51,18 +52,18 @@ type
|
||||||
cumulativeGasUsed*: UInt256
|
cumulativeGasUsed*: UInt256
|
||||||
effectiveGasPrice*: ?UInt256
|
effectiveGasPrice*: ?UInt256
|
||||||
status*: TransactionStatus
|
status*: TransactionStatus
|
||||||
transactionType*: TransactionType
|
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
|
||||||
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
|
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
|
||||||
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
|
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
|
||||||
Topic* = array[32, byte]
|
Topic* = array[32, byte]
|
||||||
Block* = object
|
Block* {.serialize.} = object
|
||||||
number*: ?UInt256
|
number*: ?UInt256
|
||||||
timestamp*: UInt256
|
timestamp*: UInt256
|
||||||
hash*: ?BlockHash
|
hash*: ?BlockHash
|
||||||
PastTransaction* = object
|
PastTransaction* {.serialize.} = object
|
||||||
blockHash*: BlockHash
|
blockHash*: BlockHash
|
||||||
blockNumber*: UInt256
|
blockNumber*: UInt256
|
||||||
sender*: Address
|
sender* {.serialize("from"), deserialize("from").}: Address
|
||||||
gas*: UInt256
|
gas*: UInt256
|
||||||
gasPrice*: UInt256
|
gasPrice*: UInt256
|
||||||
hash*: TransactionHash
|
hash*: TransactionHash
|
||||||
|
@ -70,7 +71,7 @@ type
|
||||||
nonce*: UInt256
|
nonce*: UInt256
|
||||||
to*: Address
|
to*: Address
|
||||||
transactionIndex*: UInt256
|
transactionIndex*: UInt256
|
||||||
transactionType*: ?TransactionType
|
transactionType* {.serialize("type"), deserialize("type").}: ?TransactionType
|
||||||
chainId*: ?UInt256
|
chainId*: ?UInt256
|
||||||
value*: UInt256
|
value*: UInt256
|
||||||
v*, r*, s*: UInt256
|
v*, r*, s*: UInt256
|
||||||
|
@ -87,77 +88,104 @@ template raiseProviderError(msg: string) =
|
||||||
func toTransaction*(past: PastTransaction): Transaction =
|
func toTransaction*(past: PastTransaction): Transaction =
|
||||||
Transaction(
|
Transaction(
|
||||||
sender: some past.sender,
|
sender: some past.sender,
|
||||||
gasPrice: some past.gasPrice,
|
|
||||||
data: past.input,
|
|
||||||
nonce: some past.nonce,
|
|
||||||
to: past.to,
|
to: past.to,
|
||||||
transactionType: past.transactionType,
|
data: past.input,
|
||||||
|
value: past.value,
|
||||||
|
nonce: some past.nonce,
|
||||||
|
chainId: past.chainId,
|
||||||
|
gasPrice: some past.gasPrice,
|
||||||
gasLimit: some past.gas,
|
gasLimit: some past.gas,
|
||||||
chainId: past.chainId
|
transactionType: past.transactionType
|
||||||
)
|
)
|
||||||
|
|
||||||
method getBlockNumber*(provider: Provider): Future[UInt256] {.base, gcsafe.} =
|
method getBlockNumber*(
|
||||||
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getBlock*(provider: Provider, tag: BlockTag): Future[?Block] {.base, gcsafe.} =
|
method getBlock*(
|
||||||
|
provider: Provider,
|
||||||
|
tag: BlockTag): Future[?Block] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method call*(provider: Provider,
|
method call*(
|
||||||
tx: Transaction,
|
provider: Provider,
|
||||||
blockTag = BlockTag.latest): Future[seq[byte]] {.base, gcsafe.} =
|
tx: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[seq[byte]] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(provider: Provider): Future[UInt256] {.base, gcsafe.} =
|
method getGasPrice*(
|
||||||
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionCount*(provider: Provider,
|
method getTransactionCount*(
|
||||||
address: Address,
|
provider: Provider,
|
||||||
blockTag = BlockTag.latest):
|
address: Address,
|
||||||
Future[UInt256] {.base, gcsafe.} =
|
blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransaction*(provider: Provider,
|
method getTransaction*(
|
||||||
txHash: TransactionHash):
|
provider: Provider,
|
||||||
Future[?PastTransaction] {.base, gcsafe.} =
|
txHash: TransactionHash): Future[?PastTransaction] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionReceipt*(provider: Provider,
|
method getTransactionReceipt*(
|
||||||
txHash: TransactionHash):
|
provider: Provider,
|
||||||
Future[?TransactionReceipt] {.base, gcsafe.} =
|
txHash: TransactionHash): Future[?TransactionReceipt] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(provider: Provider,
|
method sendTransaction*(
|
||||||
rawTransaction: seq[byte]):
|
provider: Provider,
|
||||||
Future[TransactionResponse] {.base, gcsafe.} =
|
rawTransaction: seq[byte]): Future[TransactionResponse] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getLogs*(provider: Provider,
|
method getLogs*(
|
||||||
filter: EventFilter): Future[seq[Log]] {.base, gcsafe.} =
|
provider: Provider,
|
||||||
|
filter: EventFilter): Future[seq[Log]] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method estimateGas*(provider: Provider,
|
method estimateGas*(
|
||||||
transaction: Transaction,
|
provider: Provider,
|
||||||
blockTag = BlockTag.latest): Future[UInt256] {.base, gcsafe.} =
|
transaction: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getChainId*(provider: Provider): Future[UInt256] {.base, gcsafe.} =
|
method getChainId*(
|
||||||
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(provider: Provider,
|
method subscribe*(
|
||||||
filter: EventFilter,
|
provider: Provider,
|
||||||
callback: LogHandler):
|
filter: EventFilter,
|
||||||
Future[Subscription] {.base, gcsafe.} =
|
callback: LogHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(provider: Provider,
|
method subscribe*(
|
||||||
callback: BlockHandler):
|
provider: Provider,
|
||||||
Future[Subscription] {.base, gcsafe.} =
|
callback: BlockHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method unsubscribe*(subscription: Subscription) {.base, async.} =
|
method unsubscribe*(
|
||||||
|
subscription: Subscription) {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
proc replay*(provider: Provider, tx: Transaction, blockNumber: UInt256) {.async.} =
|
proc replay*(
|
||||||
|
provider: Provider,
|
||||||
|
tx: Transaction,
|
||||||
|
blockNumber: UInt256) {.async: (raises:[ProviderError]).} =
|
||||||
# Replay transaction at block. Useful for fetching revert reasons, which will
|
# Replay transaction at block. Useful for fetching revert reasons, which will
|
||||||
# be present in the raised error message. The replayed block number should
|
# be present in the raised error message. The replayed block number should
|
||||||
# include the state of the chain in the block previous to the block in which
|
# include the state of the chain in the block previous to the block in which
|
||||||
|
@ -171,8 +199,7 @@ proc replay*(provider: Provider, tx: Transaction, blockNumber: UInt256) {.async.
|
||||||
method getRevertReason*(
|
method getRevertReason*(
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
hash: TransactionHash,
|
hash: TransactionHash,
|
||||||
blockNumber: UInt256
|
blockNumber: UInt256): Future[?string] {.base, async: (raises: [ProviderError]).} =
|
||||||
): Future[?string] {.base, async.} =
|
|
||||||
|
|
||||||
without pastTx =? await provider.getTransaction(hash):
|
without pastTx =? await provider.getTransaction(hash):
|
||||||
return none string
|
return none string
|
||||||
|
@ -186,8 +213,7 @@ method getRevertReason*(
|
||||||
|
|
||||||
method getRevertReason*(
|
method getRevertReason*(
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
receipt: TransactionReceipt
|
receipt: TransactionReceipt): Future[?string] {.base, async: (raises: [ProviderError]).} =
|
||||||
): Future[?string] {.base, async.} =
|
|
||||||
|
|
||||||
if receipt.status != TransactionStatus.Failure:
|
if receipt.status != TransactionStatus.Failure:
|
||||||
return none string
|
return none string
|
||||||
|
@ -199,8 +225,7 @@ method getRevertReason*(
|
||||||
|
|
||||||
proc ensureSuccess(
|
proc ensureSuccess(
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
receipt: TransactionReceipt
|
receipt: TransactionReceipt) {.async: (raises: [ProviderError]).} =
|
||||||
) {.async, raises: [ProviderError].} =
|
|
||||||
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
||||||
## reason, after which a ProviderError with the revert reason is raised.
|
## reason, after which a ProviderError with the revert reason is raised.
|
||||||
## If no revert reason was obtained
|
## If no revert reason was obtained
|
||||||
|
@ -219,11 +244,12 @@ proc ensureSuccess(
|
||||||
trace "transaction replay completed, no revert reason obtained"
|
trace "transaction replay completed, no revert reason obtained"
|
||||||
raiseProviderError("Transaction reverted with unknown reason")
|
raiseProviderError("Transaction reverted with unknown reason")
|
||||||
|
|
||||||
proc confirm*(tx: TransactionResponse,
|
proc confirm*(
|
||||||
confirmations = EthersDefaultConfirmations,
|
tx: TransactionResponse,
|
||||||
timeout = EthersReceiptTimeoutBlks):
|
confirmations = EthersDefaultConfirmations,
|
||||||
Future[TransactionReceipt]
|
timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt]
|
||||||
{.async, raises: [ProviderError, EthersError].} =
|
{.async: (raises: [CancelledError, ProviderError, EthersError]).} =
|
||||||
|
|
||||||
## Waits for a transaction to be mined and for the specified number of blocks
|
## Waits for a transaction to be mined and for the specified number of blocks
|
||||||
## to pass since it was mined (confirmations).
|
## to pass since it was mined (confirmations).
|
||||||
## A timeout, in blocks, can be specified that will raise an error if too many
|
## A timeout, in blocks, can be specified that will raise an error if too many
|
||||||
|
@ -265,10 +291,10 @@ proc confirm*(tx: TransactionResponse,
|
||||||
await tx.provider.ensureSuccess(receipt)
|
await tx.provider.ensureSuccess(receipt)
|
||||||
return receipt
|
return receipt
|
||||||
|
|
||||||
proc confirm*(tx: Future[TransactionResponse],
|
proc confirm*(
|
||||||
confirmations: int = EthersDefaultConfirmations,
|
tx: Future[TransactionResponse],
|
||||||
timeout: int = EthersReceiptTimeoutBlks):
|
confirmations: int = EthersDefaultConfirmations,
|
||||||
Future[TransactionReceipt] {.async.} =
|
timeout: int = EthersReceiptTimeoutBlks): Future[TransactionReceipt] {.async.} =
|
||||||
## Convenience method that allows wait to be chained to a sendTransaction
|
## Convenience method that allows wait to be chained to a sendTransaction
|
||||||
## call, eg:
|
## call, eg:
|
||||||
## `await signer.sendTransaction(populated).confirm(3)`
|
## `await signer.sendTransaction(populated).confirm(3)`
|
||||||
|
@ -276,5 +302,5 @@ proc confirm*(tx: Future[TransactionResponse],
|
||||||
let txResp = await tx
|
let txResp = await tx
|
||||||
return await txResp.confirm(confirmations, timeout)
|
return await txResp.confirm(confirmations, timeout)
|
||||||
|
|
||||||
method close*(provider: Provider) {.async, base.} =
|
method close*(provider: Provider) {.base, async: (raises:[ProviderError]).} =
|
||||||
discard
|
discard
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import std/json
|
|
||||||
import std/tables
|
import std/tables
|
||||||
import std/uri
|
import std/uri
|
||||||
import pkg/chronicles
|
import pkg/chronicles
|
||||||
import pkg/eth/common/eth_types_json_serialization
|
import pkg/eth/common/eth_types_json_serialization
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import pkg/json_rpc/errors
|
import pkg/json_rpc/errors
|
||||||
|
import pkg/serde
|
||||||
import ../basics
|
import ../basics
|
||||||
import ../provider
|
import ../provider
|
||||||
import ../signer
|
import ../signer
|
||||||
|
@ -12,7 +12,6 @@ import ./jsonrpc/rpccalls
|
||||||
import ./jsonrpc/conversions
|
import ./jsonrpc/conversions
|
||||||
import ./jsonrpc/subscriptions
|
import ./jsonrpc/subscriptions
|
||||||
|
|
||||||
export json
|
|
||||||
export basics
|
export basics
|
||||||
export provider
|
export provider
|
||||||
export chronicles
|
export chronicles
|
||||||
|
@ -26,20 +25,25 @@ type
|
||||||
JsonRpcProvider* = ref object of Provider
|
JsonRpcProvider* = ref object of Provider
|
||||||
client: Future[RpcClient]
|
client: Future[RpcClient]
|
||||||
subscriptions: Future[JsonRpcSubscriptions]
|
subscriptions: Future[JsonRpcSubscriptions]
|
||||||
JsonRpcSigner* = ref object of Signer
|
|
||||||
provider: JsonRpcProvider
|
|
||||||
address: ?Address
|
|
||||||
JsonRpcProviderError* = object of ProviderError
|
JsonRpcProviderError* = object of ProviderError
|
||||||
JsonRpcSubscription* = ref object of Subscription
|
JsonRpcSubscription* = ref object of Subscription
|
||||||
subscriptions: JsonRpcSubscriptions
|
subscriptions: JsonRpcSubscriptions
|
||||||
id: JsonNode
|
id: JsonNode
|
||||||
|
|
||||||
proc raiseJsonRpcProviderError(message: string) {.raises: [JsonRpcProviderError].} =
|
# Signer
|
||||||
|
JsonRpcSigner* = ref object of Signer
|
||||||
|
provider: JsonRpcProvider
|
||||||
|
address: ?Address
|
||||||
|
JsonRpcSignerError* = object of SignerError
|
||||||
|
|
||||||
|
proc raiseJsonRpcProviderError(
|
||||||
|
message: string) {.raises: [JsonRpcProviderError].} =
|
||||||
|
|
||||||
var message = message
|
var message = message
|
||||||
try:
|
if json =? JsonNode.fromJson(message):
|
||||||
message = parseJson(message){"message"}.getStr
|
if "message" in json:
|
||||||
except Exception:
|
message = json{"message"}.getStr
|
||||||
discard
|
|
||||||
raise newException(JsonRpcProviderError, message)
|
raise newException(JsonRpcProviderError, message)
|
||||||
|
|
||||||
template convertError(body) =
|
template convertError(body) =
|
||||||
|
@ -47,9 +51,7 @@ template convertError(body) =
|
||||||
body
|
body
|
||||||
except JsonRpcError as error:
|
except JsonRpcError as error:
|
||||||
raiseJsonRpcProviderError(error.msg)
|
raiseJsonRpcProviderError(error.msg)
|
||||||
# Catch all ValueErrors for now, at least until JsonRpcError is actually
|
except CatchableError as error:
|
||||||
# raised. PR created: https://github.com/status-im/nim-json-rpc/pull/151
|
|
||||||
except ValueError as error:
|
|
||||||
raiseJsonRpcProviderError(error.msg)
|
raiseJsonRpcProviderError(error.msg)
|
||||||
|
|
||||||
# Provider
|
# Provider
|
||||||
|
@ -60,48 +62,68 @@ const defaultPollingInterval = 4.seconds
|
||||||
proc jsonHeaders: seq[(string, string)] =
|
proc jsonHeaders: seq[(string, string)] =
|
||||||
@[("Content-Type", "application/json")]
|
@[("Content-Type", "application/json")]
|
||||||
|
|
||||||
proc new*(_: type JsonRpcProvider,
|
proc new*(
|
||||||
url=defaultUrl,
|
_: type JsonRpcProvider,
|
||||||
pollingInterval=defaultPollingInterval): JsonRpcProvider =
|
url=defaultUrl,
|
||||||
|
pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
||||||
|
|
||||||
var initialized: Future[void]
|
var initialized: Future[void]
|
||||||
var client: RpcClient
|
var client: RpcClient
|
||||||
var subscriptions: JsonRpcSubscriptions
|
var subscriptions: JsonRpcSubscriptions
|
||||||
|
|
||||||
proc initialize {.async.} =
|
proc initialize {.async: (raises:[JsonRpcProviderError]).} =
|
||||||
case parseUri(url).scheme
|
convertError:
|
||||||
of "ws", "wss":
|
case parseUri(url).scheme
|
||||||
let websocket = newRpcWebSocketClient(getHeaders = jsonHeaders)
|
of "ws", "wss":
|
||||||
await websocket.connect(url)
|
let websocket = newRpcWebSocketClient(getHeaders = jsonHeaders)
|
||||||
client = websocket
|
await websocket.connect(url)
|
||||||
subscriptions = JsonRpcSubscriptions.new(websocket)
|
client = websocket
|
||||||
else:
|
subscriptions = JsonRpcSubscriptions.new(websocket)
|
||||||
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
else:
|
||||||
await http.connect(url)
|
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
||||||
client = http
|
await http.connect(url)
|
||||||
subscriptions = JsonRpcSubscriptions.new(http,
|
client = http
|
||||||
pollingInterval = pollingInterval)
|
subscriptions = JsonRpcSubscriptions.new(http,
|
||||||
|
pollingInterval = pollingInterval)
|
||||||
|
subscriptions.start()
|
||||||
|
|
||||||
proc awaitClient: Future[RpcClient] {.async.} =
|
proc awaitClient: Future[RpcClient] {.async:(raises:[JsonRpcProviderError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
return client
|
return client
|
||||||
|
|
||||||
proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async.} =
|
proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async:(raises:[JsonRpcProviderError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
return subscriptions
|
return subscriptions
|
||||||
|
|
||||||
initialized = initialize()
|
initialized = initialize()
|
||||||
JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
||||||
|
|
||||||
|
proc callImpl(
|
||||||
|
client: RpcClient,
|
||||||
|
call: string,
|
||||||
|
args: JsonNode): Future[JsonNode] {.async: (raises: [JsonRpcProviderError]).} =
|
||||||
|
|
||||||
|
without response =? (await client.call(call, %args)).catch, error:
|
||||||
|
raiseJsonRpcProviderError error.msg
|
||||||
|
without json =? JsonNode.fromJson(response.string), error:
|
||||||
|
raiseJsonRpcProviderError "Failed to parse response: " & error.msg
|
||||||
|
json
|
||||||
|
|
||||||
|
proc send*(
|
||||||
|
provider: JsonRpcProvider,
|
||||||
|
call: string,
|
||||||
|
arguments: seq[JsonNode] = @[]): Future[JsonNode]
|
||||||
|
{.async: (raises: [JsonRpcProviderError]).} =
|
||||||
|
|
||||||
proc send*(provider: JsonRpcProvider,
|
|
||||||
call: string,
|
|
||||||
arguments: seq[JsonNode] = @[]): Future[JsonNode] {.async.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.call(call, %arguments)
|
return await client.callImpl(call, %arguments)
|
||||||
|
|
||||||
|
proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]]
|
||||||
|
{.async: (raises: [JsonRpcProviderError]).} =
|
||||||
|
|
||||||
proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]] {.async.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_accounts()
|
return await client.eth_accounts()
|
||||||
|
@ -112,54 +134,66 @@ proc getSigner*(provider: JsonRpcProvider): JsonRpcSigner =
|
||||||
proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
||||||
JsonRpcSigner(provider: provider, address: some address)
|
JsonRpcSigner(provider: provider, address: some address)
|
||||||
|
|
||||||
method getBlockNumber*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
method getBlockNumber*(
|
||||||
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_blockNumber()
|
return await client.eth_blockNumber()
|
||||||
|
|
||||||
method getBlock*(provider: JsonRpcProvider,
|
method getBlock*(
|
||||||
tag: BlockTag): Future[?Block] {.async.} =
|
provider: JsonRpcProvider,
|
||||||
|
tag: BlockTag): Future[?Block] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getBlockByNumber(tag, false)
|
return await client.eth_getBlockByNumber(tag, false)
|
||||||
|
|
||||||
method call*(provider: JsonRpcProvider,
|
method call*(
|
||||||
tx: Transaction,
|
provider: JsonRpcProvider,
|
||||||
blockTag = BlockTag.latest): Future[seq[byte]] {.async.} =
|
tx: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[seq[byte]] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_call(tx, blockTag)
|
return await client.eth_call(tx, blockTag)
|
||||||
|
|
||||||
method getGasPrice*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
method getGasPrice*(
|
||||||
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_gasPrice()
|
return await client.eth_gasPrice()
|
||||||
|
|
||||||
method getTransactionCount*(provider: JsonRpcProvider,
|
method getTransactionCount*(
|
||||||
address: Address,
|
provider: JsonRpcProvider,
|
||||||
blockTag = BlockTag.latest):
|
address: Address,
|
||||||
Future[UInt256] {.async.} =
|
blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionCount(address, blockTag)
|
return await client.eth_getTransactionCount(address, blockTag)
|
||||||
|
|
||||||
method getTransaction*(provider: JsonRpcProvider,
|
method getTransaction*(
|
||||||
txHash: TransactionHash):
|
provider: JsonRpcProvider,
|
||||||
Future[?PastTransaction] {.async.} =
|
txHash: TransactionHash): Future[?PastTransaction] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionByHash(txHash)
|
return await client.eth_getTransactionByHash(txHash)
|
||||||
|
|
||||||
method getTransactionReceipt*(provider: JsonRpcProvider,
|
method getTransactionReceipt*(
|
||||||
txHash: TransactionHash):
|
provider: JsonRpcProvider,
|
||||||
Future[?TransactionReceipt] {.async.} =
|
txHash: TransactionHash): Future[?TransactionReceipt] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionReceipt(txHash)
|
return await client.eth_getTransactionReceipt(txHash)
|
||||||
|
|
||||||
method getLogs*(provider: JsonRpcProvider,
|
method getLogs*(
|
||||||
filter: EventFilter):
|
provider: JsonRpcProvider,
|
||||||
Future[seq[Log]] {.async.} =
|
filter: EventFilter): Future[seq[Log]] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let logsJson = if filter of Filter:
|
let logsJson = if filter of Filter:
|
||||||
|
@ -171,19 +205,23 @@ method getLogs*(provider: JsonRpcProvider,
|
||||||
|
|
||||||
var logs: seq[Log] = @[]
|
var logs: seq[Log] = @[]
|
||||||
for logJson in logsJson.getElems:
|
for logJson in logsJson.getElems:
|
||||||
if log =? Log.fromJson(logJson).catch:
|
if log =? Log.fromJson(logJson):
|
||||||
logs.add log
|
logs.add log
|
||||||
|
|
||||||
return logs
|
return logs
|
||||||
|
|
||||||
method estimateGas*(provider: JsonRpcProvider,
|
method estimateGas*(
|
||||||
transaction: Transaction,
|
provider: JsonRpcProvider,
|
||||||
blockTag = BlockTag.latest): Future[UInt256] {.async.} =
|
transaction: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_estimateGas(transaction, blockTag)
|
return await client.eth_estimateGas(transaction, blockTag)
|
||||||
|
|
||||||
method getChainId*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
method getChainId*(
|
||||||
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
try:
|
try:
|
||||||
|
@ -191,7 +229,11 @@ method getChainId*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return parse(await client.net_version(), UInt256)
|
return parse(await client.net_version(), UInt256)
|
||||||
|
|
||||||
method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): Future[TransactionResponse] {.async.} =
|
method sendTransaction*(
|
||||||
|
provider: JsonRpcProvider,
|
||||||
|
rawTransaction: seq[byte]): Future[TransactionResponse]
|
||||||
|
{.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let
|
let
|
||||||
client = await provider.client
|
client = await provider.client
|
||||||
|
@ -199,30 +241,36 @@ method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): F
|
||||||
|
|
||||||
return TransactionResponse(hash: hash, provider: provider)
|
return TransactionResponse(hash: hash, provider: provider)
|
||||||
|
|
||||||
method subscribe*(provider: JsonRpcProvider,
|
method subscribe*(
|
||||||
filter: EventFilter,
|
provider: JsonRpcProvider,
|
||||||
onLog: LogHandler):
|
filter: EventFilter,
|
||||||
Future[Subscription] {.async.} =
|
onLog: LogHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeLogs(filter, onLog)
|
let id = await subscriptions.subscribeLogs(filter, onLog)
|
||||||
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
||||||
|
|
||||||
method subscribe*(provider: JsonRpcProvider,
|
method subscribe*(
|
||||||
onBlock: BlockHandler):
|
provider: JsonRpcProvider,
|
||||||
Future[Subscription] {.async.} =
|
onBlock: BlockHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeBlocks(onBlock)
|
let id = await subscriptions.subscribeBlocks(onBlock)
|
||||||
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
||||||
|
|
||||||
method unsubscribe(subscription: JsonRpcSubscription) {.async.} =
|
method unsubscribe*(
|
||||||
|
subscription: JsonRpcSubscription) {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = subscription.subscriptions
|
let subscriptions = subscription.subscriptions
|
||||||
let id = subscription.id
|
let id = subscription.id
|
||||||
await subscriptions.unsubscribe(id)
|
await subscriptions.unsubscribe(id)
|
||||||
|
|
||||||
method close*(provider: JsonRpcProvider) {.async.} =
|
method close*(
|
||||||
|
provider: JsonRpcProvider) {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
|
@ -231,10 +279,32 @@ method close*(provider: JsonRpcProvider) {.async.} =
|
||||||
|
|
||||||
# Signer
|
# Signer
|
||||||
|
|
||||||
method provider*(signer: JsonRpcSigner): Provider =
|
proc raiseJsonRpcSignerError(
|
||||||
|
message: string) {.raises: [JsonRpcSignerError].} =
|
||||||
|
|
||||||
|
var message = message
|
||||||
|
if json =? JsonNode.fromJson(message):
|
||||||
|
if "message" in json:
|
||||||
|
message = json{"message"}.getStr
|
||||||
|
raise newException(JsonRpcSignerError, message)
|
||||||
|
|
||||||
|
template convertSignerError(body) =
|
||||||
|
try:
|
||||||
|
body
|
||||||
|
except JsonRpcError as error:
|
||||||
|
raiseJsonRpcSignerError(error.msg)
|
||||||
|
except CatchableError as error:
|
||||||
|
raise newException(JsonRpcSignerError, error.msg)
|
||||||
|
|
||||||
|
method provider*(signer: JsonRpcSigner): Provider
|
||||||
|
{.gcsafe, raises: [SignerError].} =
|
||||||
|
|
||||||
signer.provider
|
signer.provider
|
||||||
|
|
||||||
method getAddress*(signer: JsonRpcSigner): Future[Address] {.async.} =
|
method getAddress*(
|
||||||
|
signer: JsonRpcSigner): Future[Address]
|
||||||
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
if address =? signer.address:
|
if address =? signer.address:
|
||||||
return address
|
return address
|
||||||
|
|
||||||
|
@ -242,18 +312,23 @@ method getAddress*(signer: JsonRpcSigner): Future[Address] {.async.} =
|
||||||
if accounts.len > 0:
|
if accounts.len > 0:
|
||||||
return accounts[0]
|
return accounts[0]
|
||||||
|
|
||||||
raiseJsonRpcProviderError "no address found"
|
raiseJsonRpcSignerError "no address found"
|
||||||
|
|
||||||
method signMessage*(signer: JsonRpcSigner,
|
method signMessage*(
|
||||||
message: seq[byte]): Future[seq[byte]] {.async.} =
|
signer: JsonRpcSigner,
|
||||||
convertError:
|
message: seq[byte]): Future[seq[byte]] {.async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
|
convertSignerError:
|
||||||
let client = await signer.provider.client
|
let client = await signer.provider.client
|
||||||
let address = await signer.getAddress()
|
let address = await signer.getAddress()
|
||||||
return await client.eth_sign(address, message)
|
return await client.eth_sign(address, message)
|
||||||
|
|
||||||
method sendTransaction*(signer: JsonRpcSigner,
|
method sendTransaction*(
|
||||||
transaction: Transaction): Future[TransactionResponse] {.async.} =
|
signer: JsonRpcSigner,
|
||||||
convertError:
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
|
{.async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
|
convertSignerError:
|
||||||
if nonce =? transaction.nonce:
|
if nonce =? transaction.nonce:
|
||||||
signer.updateNonce(nonce)
|
signer.updateNonce(nonce)
|
||||||
let
|
let
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import std/json
|
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/strutils
|
import std/strutils
|
||||||
|
import pkg/chronicles except fromJson, `%`, `%*`, toJson
|
||||||
import pkg/json_rpc/jsonmarshal
|
import pkg/json_rpc/jsonmarshal
|
||||||
|
import pkg/questionable/results
|
||||||
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../../basics
|
import ../../basics
|
||||||
import ../../transaction
|
import ../../transaction
|
||||||
|
@ -9,68 +11,57 @@ import ../../blocktag
|
||||||
import ../../provider
|
import ../../provider
|
||||||
|
|
||||||
export jsonmarshal
|
export jsonmarshal
|
||||||
|
export serde
|
||||||
|
export chronicles except fromJson, `%`, `%*`, toJson
|
||||||
|
|
||||||
type JsonSerializationError = object of EthersError
|
{.push raises: [].}
|
||||||
|
|
||||||
template raiseSerializationError(message: string) =
|
proc getOrRaise*[T, E](self: ?!T, exc: typedesc[E]): T {.raises: [E].} =
|
||||||
raise newException(JsonSerializationError, message)
|
let val = self.valueOr:
|
||||||
|
raise newException(E, self.error.msg)
|
||||||
|
val
|
||||||
|
|
||||||
proc expectFields(json: JsonNode, expectedFields: varargs[string]) =
|
template mapFailure*[T, V, E](
|
||||||
for fieldName in expectedFields:
|
exp: Result[T, V],
|
||||||
if not json.hasKey(fieldName):
|
exc: typedesc[E],
|
||||||
raiseSerializationError(fmt"'{fieldName}' field not found in ${json}")
|
): Result[T, ref CatchableError] =
|
||||||
|
## Convert `Result[T, E]` to `Result[E, ref CatchableError]`
|
||||||
|
##
|
||||||
|
|
||||||
func fromJson*(T: type, json: JsonNode, name = ""): T =
|
exp.mapErr(proc (e: V): ref CatchableError = (ref exc)(msg: e.msg))
|
||||||
fromJson(json, name, result)
|
|
||||||
|
|
||||||
# byte sequence
|
|
||||||
|
|
||||||
func `%`*(bytes: seq[byte]): JsonNode =
|
|
||||||
%("0x" & bytes.toHex)
|
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var seq[byte]) =
|
|
||||||
result = hexToSeqByte(json.getStr())
|
|
||||||
|
|
||||||
# byte arrays
|
|
||||||
|
|
||||||
func `%`*[N](bytes: array[N, byte]): JsonNode =
|
|
||||||
%("0x" & bytes.toHex)
|
|
||||||
|
|
||||||
func fromJson*[N](json: JsonNode, name: string, result: var array[N, byte]) =
|
|
||||||
hexToByteArray(json.getStr(), result)
|
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
func `%`*(address: Address): JsonNode =
|
func `%`*(address: Address): JsonNode =
|
||||||
%($address)
|
%($address)
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var Address) =
|
func fromJson(_: type Address, json: JsonNode): ?!Address =
|
||||||
if address =? Address.init(json.getStr()):
|
expectJsonKind(Address, JString, json)
|
||||||
result = address
|
without address =? Address.init(json.getStr), error:
|
||||||
else:
|
return failure newException(SerializationError,
|
||||||
raise newException(ValueError, "\"" & name & "\"is not an Address")
|
"Failed to convert '" & $json & "' to Address: " & error.msg)
|
||||||
|
success address
|
||||||
|
|
||||||
# UInt256
|
# UInt256
|
||||||
|
|
||||||
func `%`*(integer: UInt256): JsonNode =
|
func `%`*(integer: UInt256): JsonNode =
|
||||||
%("0x" & toHex(integer))
|
%("0x" & toHex(integer))
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var UInt256) =
|
func fromJson*(_: type UInt256, json: JsonNode): ?!UInt256 =
|
||||||
result = UInt256.fromHex(json.getStr())
|
without result =? UInt256.fromHex(json.getStr()).catch, error:
|
||||||
|
return UInt256.failure error.msg
|
||||||
# TransactionType
|
success result
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var TransactionType) =
|
|
||||||
let val = fromHex[int](json.getStr)
|
|
||||||
result = TransactionType(val)
|
|
||||||
|
|
||||||
func `%`*(txType: TransactionType): JsonNode =
|
|
||||||
%("0x" & txType.int.toHex(1))
|
|
||||||
|
|
||||||
# Transaction
|
# Transaction
|
||||||
|
|
||||||
|
# TODO: add option that ignores none Option[T]
|
||||||
|
# TODO: add name option (gasLimit => gas, sender => from)
|
||||||
func `%`*(transaction: Transaction): JsonNode =
|
func `%`*(transaction: Transaction): JsonNode =
|
||||||
result = %{ "to": %transaction.to, "data": %transaction.data }
|
result = %*{
|
||||||
|
"to": transaction.to,
|
||||||
|
"data": %transaction.data,
|
||||||
|
"value": %transaction.value
|
||||||
|
}
|
||||||
if sender =? transaction.sender:
|
if sender =? transaction.sender:
|
||||||
result["from"] = %sender
|
result["from"] = %sender
|
||||||
if nonce =? transaction.nonce:
|
if nonce =? transaction.nonce:
|
||||||
|
@ -84,105 +75,52 @@ func `%`*(transaction: Transaction): JsonNode =
|
||||||
|
|
||||||
# BlockTag
|
# BlockTag
|
||||||
|
|
||||||
func `%`*(blockTag: BlockTag): JsonNode =
|
func `%`*(tag: BlockTag): JsonNode =
|
||||||
%($blockTag)
|
% $tag
|
||||||
|
|
||||||
# Log
|
func fromJson*(_: type BlockTag, json: JsonNode): ?!BlockTag =
|
||||||
|
expectJsonKind(BlockTag, JString, json)
|
||||||
|
let jsonVal = json.getStr
|
||||||
|
if jsonVal[0..1].toLowerAscii == "0x":
|
||||||
|
without blkNum =? UInt256.fromHex(jsonVal).catch, error:
|
||||||
|
return BlockTag.failure error.msg
|
||||||
|
return success BlockTag.init(blkNum)
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var Log) =
|
case jsonVal:
|
||||||
if not (json.hasKey("data") and json.hasKey("topics")):
|
of "earliest": return success BlockTag.earliest
|
||||||
raise newException(ValueError, "'data' and/or 'topics' fields not found")
|
of "latest": return success BlockTag.latest
|
||||||
|
of "pending": return success BlockTag.pending
|
||||||
|
else: return failure newException(SerializationError,
|
||||||
|
"Failed to convert '" & $json &
|
||||||
|
"' to BlockTag: must be one of 'earliest', 'latest', 'pending'")
|
||||||
|
|
||||||
var data: seq[byte]
|
# TransactionStatus | TransactionType
|
||||||
var topics: seq[Topic]
|
|
||||||
fromJson(json["data"], "data", data)
|
|
||||||
fromJson(json["topics"], "topics", topics)
|
|
||||||
result = Log(data: data, topics: topics)
|
|
||||||
|
|
||||||
# TransactionStatus
|
func `%`*(e: TransactionStatus | TransactionType): JsonNode =
|
||||||
|
% ("0x" & e.int8.toHex(1))
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var TransactionStatus) =
|
proc fromJson*[E: TransactionStatus | TransactionType](
|
||||||
let val = fromHex[int](json.getStr)
|
T: type E,
|
||||||
result = TransactionStatus(val)
|
json: JsonNode
|
||||||
|
): ?!T =
|
||||||
|
expectJsonKind(string, JString, json)
|
||||||
|
let integer = ? fromHex[int](json.str).catch.mapFailure(SerializationError)
|
||||||
|
success T(integer)
|
||||||
|
|
||||||
func `%`*(status: TransactionStatus): JsonNode =
|
## Generic conversions to use nim-json instead of nim-json-serialization for
|
||||||
%("0x" & status.int.toHex(1))
|
## json rpc serialization purposes
|
||||||
|
## writeValue => `%`
|
||||||
|
## readValue => fromJson
|
||||||
|
|
||||||
# PastTransaction
|
proc writeValue*[T: not JsonNode](
|
||||||
|
writer: var JsonWriter[JrpcConv],
|
||||||
|
value: T) {.raises:[IOError].} =
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var PastTransaction) =
|
writer.writeValue(%value)
|
||||||
# Deserializes a past transaction, eg eth_getTransactionByHash.
|
|
||||||
# Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash
|
|
||||||
json.expectFields "blockHash", "blockNumber", "from", "gas", "gasPrice",
|
|
||||||
"hash", "input", "nonce", "to", "transactionIndex", "value",
|
|
||||||
"v", "r", "s"
|
|
||||||
|
|
||||||
result = PastTransaction(
|
proc readValue*[T: not JsonNode](
|
||||||
blockHash: BlockHash.fromJson(json["blockHash"], "blockHash"),
|
r: var JsonReader[JrpcConv],
|
||||||
blockNumber: UInt256.fromJson(json["blockNumber"], "blockNumber"),
|
result: var T) {.raises: [SerializationError, IOError].} =
|
||||||
sender: Address.fromJson(json["from"], "from"),
|
|
||||||
gas: UInt256.fromJson(json["gas"], "gas"),
|
|
||||||
gasPrice: UInt256.fromJson(json["gasPrice"], "gasPrice"),
|
|
||||||
hash: TransactionHash.fromJson(json["hash"], "hash"),
|
|
||||||
input: seq[byte].fromJson(json["input"], "input"),
|
|
||||||
nonce: UInt256.fromJson(json["nonce"], "nonce"),
|
|
||||||
to: Address.fromJson(json["to"], "to"),
|
|
||||||
transactionIndex: UInt256.fromJson(json["transactionIndex"], "transactionIndex"),
|
|
||||||
value: UInt256.fromJson(json["value"], "value"),
|
|
||||||
v: UInt256.fromJson(json["v"], "v"),
|
|
||||||
r: UInt256.fromJson(json["r"], "r"),
|
|
||||||
s: UInt256.fromJson(json["s"], "s"),
|
|
||||||
)
|
|
||||||
if json.hasKey("type"):
|
|
||||||
result.transactionType = fromJson(?TransactionType, json["type"], "type")
|
|
||||||
if json.hasKey("chainId"):
|
|
||||||
result.chainId = fromJson(?UInt256, json["chainId"], "chainId")
|
|
||||||
|
|
||||||
func `%`*(tx: PastTransaction): JsonNode =
|
var json = r.readValue(JsonNode)
|
||||||
let json = %*{
|
result = T.fromJson(json).getOrRaise(SerializationError)
|
||||||
"blockHash": tx.blockHash,
|
|
||||||
"blockNumber": tx.blockNumber,
|
|
||||||
"from": tx.sender,
|
|
||||||
"gas": tx.gas,
|
|
||||||
"gasPrice": tx.gasPrice,
|
|
||||||
"hash": tx.hash,
|
|
||||||
"input": tx.input,
|
|
||||||
"nonce": tx.nonce,
|
|
||||||
"to": tx.to,
|
|
||||||
"transactionIndex": tx.transactionIndex,
|
|
||||||
"value": tx.value,
|
|
||||||
"v": tx.v,
|
|
||||||
"r": tx.r,
|
|
||||||
"s": tx.s
|
|
||||||
}
|
|
||||||
if txType =? tx.transactionType:
|
|
||||||
json["type"] = %txType
|
|
||||||
if chainId =? tx.chainId:
|
|
||||||
json["chainId"] = %chainId
|
|
||||||
return json
|
|
||||||
|
|
||||||
# TransactionReceipt
|
|
||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var TransactionReceipt) =
|
|
||||||
# Deserializes a transaction receipt, eg eth_getTransactionReceipt.
|
|
||||||
# Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
|
|
||||||
json.expectFields "transactionHash", "transactionIndex", "cumulativeGasUsed",
|
|
||||||
"effectiveGasPrice", "gasUsed", "logs", "logsBloom", "type",
|
|
||||||
"status"
|
|
||||||
|
|
||||||
result = TransactionReceipt(
|
|
||||||
transactionHash: fromJson(TransactionHash, json["transactionHash"], "transactionHash"),
|
|
||||||
transactionIndex: UInt256.fromJson(json["transactionIndex"], "transactionIndex"),
|
|
||||||
blockHash: fromJson(?BlockHash, json["blockHash"], "blockHash"),
|
|
||||||
blockNumber: fromJson(?UInt256, json["blockNumber"], "blockNumber"),
|
|
||||||
sender: fromJson(?Address, json["from"], "from"),
|
|
||||||
to: fromJson(?Address, json["to"], "to"),
|
|
||||||
cumulativeGasUsed: UInt256.fromJson(json["cumulativeGasUsed"], "cumulativeGasUsed"),
|
|
||||||
effectiveGasPrice: fromJson(?UInt256, json["effectiveGasPrice"], "effectiveGasPrice"),
|
|
||||||
gasUsed: UInt256.fromJson(json["gasUsed"], "gasUsed"),
|
|
||||||
contractAddress: fromJson(?Address, json["contractAddress"], "contractAddress"),
|
|
||||||
logs: seq[Log].fromJson(json["logs"], "logs"),
|
|
||||||
logsBloom: seq[byte].fromJson(json["logsBloom"], "logsBloom"),
|
|
||||||
transactionType: TransactionType.fromJson(json["type"], "type"),
|
|
||||||
status: TransactionStatus.fromJson(json["status"], "status")
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,5 +2,5 @@ template untilCancelled*(body) =
|
||||||
try:
|
try:
|
||||||
while true:
|
while true:
|
||||||
body
|
body
|
||||||
except CancelledError:
|
except CancelledError as e:
|
||||||
raise
|
raise e
|
||||||
|
|
|
@ -4,6 +4,7 @@ import pkg/chronos
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import ../../basics
|
import ../../basics
|
||||||
import ../../provider
|
import ../../provider
|
||||||
|
include ../../nimshims/hashes
|
||||||
import ./rpccalls
|
import ./rpccalls
|
||||||
import ./conversions
|
import ./conversions
|
||||||
import ./looping
|
import ./looping
|
||||||
|
@ -12,8 +13,41 @@ type
|
||||||
JsonRpcSubscriptions* = ref object of RootObj
|
JsonRpcSubscriptions* = ref object of RootObj
|
||||||
client: RpcClient
|
client: RpcClient
|
||||||
callbacks: Table[JsonNode, SubscriptionCallback]
|
callbacks: Table[JsonNode, SubscriptionCallback]
|
||||||
|
methodHandlers: Table[string, MethodHandler]
|
||||||
|
MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
|
||||||
SubscriptionCallback = proc(id, arguments: JsonNode) {.gcsafe, raises:[].}
|
SubscriptionCallback = proc(id, arguments: JsonNode) {.gcsafe, raises:[].}
|
||||||
|
|
||||||
|
{.push raises:[].}
|
||||||
|
|
||||||
|
template `or`(a: JsonNode, b: typed): JsonNode =
|
||||||
|
if a.isNil: b else: a
|
||||||
|
|
||||||
|
func start*(subscriptions: JsonRpcSubscriptions) =
|
||||||
|
subscriptions.client.onProcessMessage =
|
||||||
|
proc(client: RpcClient,
|
||||||
|
line: string): Result[bool, string] {.gcsafe, raises: [].} =
|
||||||
|
if json =? JsonNode.fromJson(line):
|
||||||
|
if "method" in json:
|
||||||
|
let methodName = json{"method"}.getStr()
|
||||||
|
if methodName in subscriptions.methodHandlers:
|
||||||
|
let handler = subscriptions.methodHandlers.getOrDefault(methodName)
|
||||||
|
if not handler.isNil:
|
||||||
|
handler(json{"params"} or newJArray())
|
||||||
|
# false = do not continue processing message using json_rpc's
|
||||||
|
# default processing handler
|
||||||
|
return ok false
|
||||||
|
|
||||||
|
# true = continue processing message using json_rpc's default message handler
|
||||||
|
return ok true
|
||||||
|
|
||||||
|
|
||||||
|
proc setMethodHandler(
|
||||||
|
subscriptions: JsonRpcSubscriptions,
|
||||||
|
`method`: string,
|
||||||
|
handler: MethodHandler
|
||||||
|
) =
|
||||||
|
subscriptions.methodHandlers[`method`] = handler
|
||||||
|
|
||||||
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
||||||
onBlock: BlockHandler):
|
onBlock: BlockHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
|
@ -40,11 +74,11 @@ method close*(subscriptions: JsonRpcSubscriptions) {.async, base.} =
|
||||||
proc getCallback(subscriptions: JsonRpcSubscriptions,
|
proc getCallback(subscriptions: JsonRpcSubscriptions,
|
||||||
id: JsonNode): ?SubscriptionCallback =
|
id: JsonNode): ?SubscriptionCallback =
|
||||||
try:
|
try:
|
||||||
if subscriptions.callbacks.hasKey(id):
|
if not id.isNil and id in subscriptions.callbacks:
|
||||||
subscriptions.callbacks[id].some
|
subscriptions.callbacks[id].some
|
||||||
else:
|
else:
|
||||||
SubscriptionCallback.none
|
SubscriptionCallback.none
|
||||||
except Exception:
|
except KeyError:
|
||||||
SubscriptionCallback.none
|
SubscriptionCallback.none
|
||||||
|
|
||||||
# Web sockets
|
# Web sockets
|
||||||
|
@ -54,20 +88,21 @@ type
|
||||||
|
|
||||||
proc new*(_: type JsonRpcSubscriptions,
|
proc new*(_: type JsonRpcSubscriptions,
|
||||||
client: RpcWebSocketClient): JsonRpcSubscriptions =
|
client: RpcWebSocketClient): JsonRpcSubscriptions =
|
||||||
|
|
||||||
let subscriptions = WebSocketSubscriptions(client: client)
|
let subscriptions = WebSocketSubscriptions(client: client)
|
||||||
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
|
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
|
||||||
if id =? arguments["subscription"].catch and
|
let id = arguments{"subscription"} or newJString("")
|
||||||
callback =? subscriptions.getCallback(id):
|
if callback =? subscriptions.getCallback(id):
|
||||||
callback(id, arguments)
|
callback(id, arguments)
|
||||||
client.setMethodHandler("eth_subscription", subscriptionHandler)
|
subscriptions.setMethodHandler("eth_subscription", subscriptionHandler)
|
||||||
subscriptions
|
subscriptions
|
||||||
|
|
||||||
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
||||||
onBlock: BlockHandler):
|
onBlock: BlockHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async.} =
|
{.async.} =
|
||||||
proc callback(id, arguments: JsonNode) =
|
proc callback(id, arguments: JsonNode) {.raises: [].} =
|
||||||
if blck =? Block.fromJson(arguments["result"]).catch:
|
if blck =? Block.fromJson(arguments{"result"}):
|
||||||
onBlock(blck)
|
onBlock(blck)
|
||||||
let id = await subscriptions.client.eth_subscribe("newHeads")
|
let id = await subscriptions.client.eth_subscribe("newHeads")
|
||||||
subscriptions.callbacks[id] = callback
|
subscriptions.callbacks[id] = callback
|
||||||
|
@ -79,13 +114,13 @@ method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async.} =
|
{.async.} =
|
||||||
proc callback(id, arguments: JsonNode) =
|
proc callback(id, arguments: JsonNode) =
|
||||||
if log =? Log.fromJson(arguments["result"]).catch:
|
if log =? Log.fromJson(arguments{"result"}):
|
||||||
onLog(log)
|
onLog(log)
|
||||||
let id = await subscriptions.client.eth_subscribe("logs", filter)
|
let id = await subscriptions.client.eth_subscribe("logs", filter)
|
||||||
subscriptions.callbacks[id] = callback
|
subscriptions.callbacks[id] = callback
|
||||||
return id
|
return id
|
||||||
|
|
||||||
method unsubscribe(subscriptions: WebSocketSubscriptions,
|
method unsubscribe*(subscriptions: WebSocketSubscriptions,
|
||||||
id: JsonNode)
|
id: JsonNode)
|
||||||
{.async.} =
|
{.async.} =
|
||||||
subscriptions.callbacks.del(id)
|
subscriptions.callbacks.del(id)
|
||||||
|
@ -140,7 +175,7 @@ method subscribeBlocks(subscriptions: PollingSubscriptions,
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc callback(id, change: JsonNode) =
|
proc callback(id, change: JsonNode) =
|
||||||
if hash =? BlockHash.fromJson(change).catch:
|
if hash =? BlockHash.fromJson(change):
|
||||||
asyncSpawn getBlock(hash)
|
asyncSpawn getBlock(hash)
|
||||||
|
|
||||||
let id = await subscriptions.client.eth_newBlockFilter()
|
let id = await subscriptions.client.eth_newBlockFilter()
|
||||||
|
@ -154,14 +189,14 @@ method subscribeLogs(subscriptions: PollingSubscriptions,
|
||||||
{.async.} =
|
{.async.} =
|
||||||
|
|
||||||
proc callback(id, change: JsonNode) =
|
proc callback(id, change: JsonNode) =
|
||||||
if log =? Log.fromJson(change).catch:
|
if log =? Log.fromJson(change):
|
||||||
onLog(log)
|
onLog(log)
|
||||||
|
|
||||||
let id = await subscriptions.client.eth_newFilter(filter)
|
let id = await subscriptions.client.eth_newFilter(filter)
|
||||||
subscriptions.callbacks[id] = callback
|
subscriptions.callbacks[id] = callback
|
||||||
return id
|
return id
|
||||||
|
|
||||||
method unsubscribe(subscriptions: PollingSubscriptions,
|
method unsubscribe*(subscriptions: PollingSubscriptions,
|
||||||
id: JsonNode)
|
id: JsonNode)
|
||||||
{.async.} =
|
{.async.} =
|
||||||
subscriptions.callbacks.del(id)
|
subscriptions.callbacks.del(id)
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import pkg/questionable
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./provider
|
import ./provider
|
||||||
|
|
||||||
export basics
|
export basics
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
type
|
type
|
||||||
Signer* = ref object of RootObj
|
Signer* = ref object of RootObj
|
||||||
lastSeenNonce: ?UInt256
|
lastSeenNonce: ?UInt256
|
||||||
populateLock: AsyncLock
|
populateLock: AsyncLock
|
||||||
|
|
||||||
type
|
|
||||||
SignerError* = object of EthersError
|
SignerError* = object of EthersError
|
||||||
EstimateGasError* = object of SignerError
|
EstimateGasError* = object of SignerError
|
||||||
transaction*: Transaction
|
transaction*: Transaction
|
||||||
|
@ -19,50 +20,87 @@ template raiseSignerError(message: string, parent: ref ProviderError = nil) =
|
||||||
proc raiseEstimateGasError(
|
proc raiseEstimateGasError(
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
parent: ref ProviderError = nil
|
parent: ref ProviderError = nil
|
||||||
) =
|
) {.raises: [EstimateGasError] .} =
|
||||||
let e = (ref EstimateGasError)(
|
let e = (ref EstimateGasError)(
|
||||||
msg: "Estimate gas failed",
|
msg: "Estimate gas failed",
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
parent: parent)
|
parent: parent)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
method provider*(signer: Signer): Provider {.base, gcsafe.} =
|
template convertError(body) =
|
||||||
|
try:
|
||||||
|
body
|
||||||
|
except EthersError as error:
|
||||||
|
raiseSignerError(error.msg)
|
||||||
|
except CatchableError as error:
|
||||||
|
raiseSignerError(error.msg)
|
||||||
|
|
||||||
|
method provider*(
|
||||||
|
signer: Signer): Provider {.base, gcsafe, raises: [SignerError].} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getAddress*(signer: Signer): Future[Address] {.base, gcsafe.} =
|
method getAddress*(
|
||||||
|
signer: Signer): Future[Address]
|
||||||
|
{.base, async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method signMessage*(signer: Signer,
|
method signMessage*(
|
||||||
message: seq[byte]): Future[seq[byte]] {.base, async.} =
|
signer: Signer,
|
||||||
|
message: seq[byte]): Future[seq[byte]]
|
||||||
|
{.base, async: (raises: [SignerError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(signer: Signer,
|
method sendTransaction*(
|
||||||
transaction: Transaction): Future[TransactionResponse] {.base, async.} =
|
signer: Signer,
|
||||||
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
|
{.base, async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(signer: Signer): Future[UInt256] {.base, gcsafe.} =
|
method getGasPrice*(
|
||||||
signer.provider.getGasPrice()
|
signer: Signer): Future[UInt256]
|
||||||
|
{.base, async: (raises: [ProviderError, SignerError]).} =
|
||||||
|
|
||||||
method getTransactionCount*(signer: Signer,
|
return await signer.provider.getGasPrice()
|
||||||
blockTag = BlockTag.latest):
|
|
||||||
Future[UInt256] {.base, async.} =
|
method getTransactionCount*(
|
||||||
let address = await signer.getAddress()
|
signer: Signer,
|
||||||
return await signer.provider.getTransactionCount(address, blockTag)
|
blockTag = BlockTag.latest): Future[UInt256]
|
||||||
|
{.base, async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
|
convertError:
|
||||||
|
let address = await signer.getAddress()
|
||||||
|
return await signer.provider.getTransactionCount(address, blockTag)
|
||||||
|
|
||||||
|
method estimateGas*(
|
||||||
|
signer: Signer,
|
||||||
|
transaction: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256]
|
||||||
|
{.base, async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
method estimateGas*(signer: Signer,
|
|
||||||
transaction: Transaction,
|
|
||||||
blockTag = BlockTag.latest): Future[UInt256] {.base, async.} =
|
|
||||||
var transaction = transaction
|
var transaction = transaction
|
||||||
transaction.sender = some(await signer.getAddress)
|
var address: Address
|
||||||
|
|
||||||
|
convertError:
|
||||||
|
address = await signer.getAddress
|
||||||
|
|
||||||
|
transaction.sender = some(address)
|
||||||
try:
|
try:
|
||||||
return await signer.provider.estimateGas(transaction)
|
return await signer.provider.estimateGas(transaction)
|
||||||
except ProviderError as e:
|
except ProviderError as e:
|
||||||
raiseEstimateGasError transaction, e
|
raiseEstimateGasError transaction, e
|
||||||
|
|
||||||
method getChainId*(signer: Signer): Future[UInt256] {.base, gcsafe.} =
|
method getChainId*(
|
||||||
signer.provider.getChainId()
|
signer: Signer): Future[UInt256]
|
||||||
|
{.base, async: (raises: [ProviderError, SignerError]).} =
|
||||||
|
|
||||||
|
return await signer.provider.getChainId()
|
||||||
|
|
||||||
|
method getNonce(
|
||||||
|
signer: Signer): Future[UInt256] {.base, async: (raises: [SignerError]).} =
|
||||||
|
|
||||||
method getNonce(signer: Signer): Future[UInt256] {.base, gcsafe, async.} =
|
|
||||||
var nonce = await signer.getTransactionCount(BlockTag.pending)
|
var nonce = await signer.getTransactionCount(BlockTag.pending)
|
||||||
|
|
||||||
if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce:
|
if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce:
|
||||||
|
@ -87,11 +125,16 @@ method decreaseNonce*(signer: Signer) {.base, gcsafe.} =
|
||||||
if lastSeen =? signer.lastSeenNonce and lastSeen > 0:
|
if lastSeen =? signer.lastSeenNonce and lastSeen > 0:
|
||||||
signer.lastSeenNonce = some lastSeen - 1
|
signer.lastSeenNonce = some lastSeen - 1
|
||||||
|
|
||||||
method populateTransaction*(signer: Signer,
|
method populateTransaction*(
|
||||||
transaction: Transaction):
|
signer: Signer,
|
||||||
Future[Transaction] {.base, async.} =
|
transaction: Transaction): Future[Transaction]
|
||||||
|
{.base, async: (raises: [CancelledError, AsyncLockError, ProviderError, SignerError]).} =
|
||||||
|
|
||||||
if sender =? transaction.sender and sender != await signer.getAddress():
|
var address: Address
|
||||||
|
convertError:
|
||||||
|
address = await signer.getAddress()
|
||||||
|
|
||||||
|
if sender =? transaction.sender and sender != address:
|
||||||
raiseSignerError("from address mismatch")
|
raiseSignerError("from address mismatch")
|
||||||
if chainId =? transaction.chainId and chainId != await signer.getChainId():
|
if chainId =? transaction.chainId and chainId != await signer.getChainId():
|
||||||
raiseSignerError("chain id mismatch")
|
raiseSignerError("chain id mismatch")
|
||||||
|
@ -105,7 +148,7 @@ method populateTransaction*(signer: Signer,
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if transaction.sender.isNone:
|
if transaction.sender.isNone:
|
||||||
populated.sender = some(await signer.getAddress())
|
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):
|
if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone):
|
||||||
|
@ -119,10 +162,12 @@ method populateTransaction*(signer: Signer,
|
||||||
populated.nonce = some(await signer.getNonce())
|
populated.nonce = some(await signer.getNonce())
|
||||||
try:
|
try:
|
||||||
populated.gasLimit = some(await signer.estimateGas(populated))
|
populated.gasLimit = some(await signer.estimateGas(populated))
|
||||||
except ProviderError, EstimateGasError:
|
except EstimateGasError as e:
|
||||||
let e = getCurrentException()
|
|
||||||
signer.decreaseNonce()
|
signer.decreaseNonce()
|
||||||
raise e
|
raise e
|
||||||
|
except ProviderError as e:
|
||||||
|
signer.decreaseNonce()
|
||||||
|
raiseSignerError(e.msg)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if transaction.nonce.isNone:
|
if transaction.nonce.isNone:
|
||||||
|
@ -138,7 +183,7 @@ method populateTransaction*(signer: Signer,
|
||||||
method cancelTransaction*(
|
method cancelTransaction*(
|
||||||
signer: Signer,
|
signer: Signer,
|
||||||
tx: Transaction
|
tx: Transaction
|
||||||
): Future[TransactionResponse] {.async, base.} =
|
): Future[TransactionResponse] {.base, async: (raises: [SignerError]).} =
|
||||||
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
||||||
# with the failed tx's nonce
|
# with the failed tx's nonce
|
||||||
|
|
||||||
|
@ -148,5 +193,6 @@ method cancelTransaction*(
|
||||||
raiseSignerError "transaction must have nonce"
|
raiseSignerError "transaction must have nonce"
|
||||||
|
|
||||||
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
||||||
cancelTx = await signer.populateTransaction(cancelTx)
|
convertError:
|
||||||
return await signer.sendTransaction(cancelTx)
|
cancelTx = await signer.populateTransaction(cancelTx)
|
||||||
|
return await signer.sendTransaction(cancelTx)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ../providers/jsonrpc
|
||||||
|
|
||||||
|
export provider, getAddress, signMessage, sendTransaction
|
|
@ -0,0 +1,92 @@
|
||||||
|
import eth/keys
|
||||||
|
import ../basics
|
||||||
|
import ../provider
|
||||||
|
import ../transaction
|
||||||
|
import ../signer
|
||||||
|
import ./wallet/error
|
||||||
|
import ./wallet/signing
|
||||||
|
|
||||||
|
export keys
|
||||||
|
export WalletError
|
||||||
|
export signing
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
var rng {.threadvar.}: ref HmacDrbgContext
|
||||||
|
|
||||||
|
proc getRng: ref HmacDrbgContext =
|
||||||
|
if rng.isNil:
|
||||||
|
rng = newRng()
|
||||||
|
rng
|
||||||
|
|
||||||
|
type Wallet* = ref object of Signer
|
||||||
|
privateKey*: PrivateKey
|
||||||
|
publicKey*: PublicKey
|
||||||
|
address*: Address
|
||||||
|
provider*: ?Provider
|
||||||
|
|
||||||
|
proc new*(_: type Wallet, privateKey: PrivateKey): Wallet =
|
||||||
|
let publicKey = privateKey.toPublicKey()
|
||||||
|
let address = Address.init(publicKey.toCanonicalAddress())
|
||||||
|
Wallet(privateKey: privateKey, publicKey: publicKey, address: address)
|
||||||
|
|
||||||
|
proc new*(_: type Wallet, privateKey: PrivateKey, provider: Provider): Wallet =
|
||||||
|
let wallet = Wallet.new(privateKey)
|
||||||
|
wallet.provider = some provider
|
||||||
|
wallet
|
||||||
|
|
||||||
|
proc new*(_: type Wallet, privateKey: string): ?!Wallet =
|
||||||
|
let keyResult = PrivateKey.fromHex(privateKey)
|
||||||
|
if keyResult.isErr:
|
||||||
|
return failure newException(WalletError, "invalid key: " & $keyResult.error)
|
||||||
|
success Wallet.new(keyResult.get())
|
||||||
|
|
||||||
|
proc new*(_: type Wallet, privateKey: string, provider: Provider): ?!Wallet =
|
||||||
|
let wallet = ? Wallet.new(privateKey)
|
||||||
|
wallet.provider = some provider
|
||||||
|
success wallet
|
||||||
|
|
||||||
|
proc connect*(wallet: Wallet, provider: Provider) =
|
||||||
|
wallet.provider = some provider
|
||||||
|
|
||||||
|
proc createRandom*(_: type Wallet): Wallet =
|
||||||
|
result = Wallet()
|
||||||
|
result.privateKey = PrivateKey.random(getRng()[])
|
||||||
|
result.publicKey = result.privateKey.toPublicKey()
|
||||||
|
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
||||||
|
|
||||||
|
proc createRandom*(_: type Wallet, provider: Provider): Wallet =
|
||||||
|
result = Wallet()
|
||||||
|
result.privateKey = PrivateKey.random(getRng()[])
|
||||||
|
result.publicKey = result.privateKey.toPublicKey()
|
||||||
|
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
||||||
|
result.provider = some provider
|
||||||
|
|
||||||
|
method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
||||||
|
without provider =? wallet.provider:
|
||||||
|
raiseWalletError "Wallet has no provider"
|
||||||
|
provider
|
||||||
|
|
||||||
|
method getAddress*(
|
||||||
|
wallet: Wallet): Future[Address]
|
||||||
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
|
return wallet.address
|
||||||
|
|
||||||
|
proc signTransaction*(wallet: Wallet,
|
||||||
|
transaction: Transaction): Future[seq[byte]] {.async: (raises:[WalletError]).} =
|
||||||
|
if sender =? transaction.sender and sender != wallet.address:
|
||||||
|
raiseWalletError "from address mismatch"
|
||||||
|
|
||||||
|
return wallet.privateKey.sign(transaction)
|
||||||
|
|
||||||
|
method sendTransaction*(
|
||||||
|
wallet: Wallet,
|
||||||
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
|
{.async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
|
convertError:
|
||||||
|
let signed = await signTransaction(wallet, transaction)
|
||||||
|
if nonce =? transaction.nonce:
|
||||||
|
wallet.updateNonce(nonce)
|
||||||
|
return await provider(wallet).sendTransaction(signed)
|
|
@ -0,0 +1,13 @@
|
||||||
|
import ../../signer
|
||||||
|
|
||||||
|
type
|
||||||
|
WalletError* = object of SignerError
|
||||||
|
|
||||||
|
func raiseWalletError*(message: string) {.raises: [WalletError].}=
|
||||||
|
raise newException(WalletError, message)
|
||||||
|
|
||||||
|
template convertError*(body) =
|
||||||
|
try:
|
||||||
|
body
|
||||||
|
except CatchableError as error:
|
||||||
|
raiseWalletError(error.msg)
|
|
@ -2,9 +2,9 @@ import pkg/eth/keys
|
||||||
import pkg/eth/rlp
|
import pkg/eth/rlp
|
||||||
import pkg/eth/common/transaction as eth
|
import pkg/eth/common/transaction as eth
|
||||||
import pkg/eth/common/eth_hash
|
import pkg/eth/common/eth_hash
|
||||||
import ../basics
|
import ../../basics
|
||||||
import ../transaction as ethers
|
import ../../transaction as ethers
|
||||||
import ../provider
|
import ../../provider
|
||||||
import ./error
|
import ./error
|
||||||
|
|
||||||
type
|
type
|
|
@ -27,7 +27,7 @@ proc reverts*[T](call: Future[T]): Future[bool] {.async.} =
|
||||||
else:
|
else:
|
||||||
discard await call
|
discard await call
|
||||||
return false
|
return false
|
||||||
except ProviderError, EstimateGasError:
|
except ProviderError, SignerError, EstimateGasError:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc reverts*[T](call: Future[T], reason: string): Future[bool] {.async.} =
|
proc reverts*[T](call: Future[T], reason: string): Future[bool] {.async.} =
|
||||||
|
@ -37,7 +37,7 @@ proc reverts*[T](call: Future[T], reason: string): Future[bool] {.async.} =
|
||||||
else:
|
else:
|
||||||
discard await call
|
discard await call
|
||||||
return false
|
return false
|
||||||
except ProviderError, EstimateGasError:
|
except ProviderError, SignerError, EstimateGasError:
|
||||||
let e = getCurrentException()
|
let e = getCurrentException()
|
||||||
var passed = reason == (ref EthersError)(e).revertReason
|
var passed = reason == (ref EthersError)(e).revertReason
|
||||||
if not passed and
|
if not passed and
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ./basics
|
import ./basics
|
||||||
|
|
||||||
|
@ -6,8 +7,8 @@ type
|
||||||
Legacy = 0,
|
Legacy = 0,
|
||||||
AccessList = 1,
|
AccessList = 1,
|
||||||
Dynamic = 2
|
Dynamic = 2
|
||||||
Transaction* = object
|
Transaction* {.serialize.} = object
|
||||||
sender*: ?Address
|
sender* {.serialize("from").}: ?Address
|
||||||
to*: Address
|
to*: Address
|
||||||
data*: seq[byte]
|
data*: seq[byte]
|
||||||
value*: UInt256
|
value*: UInt256
|
||||||
|
@ -17,7 +18,7 @@ type
|
||||||
maxFee*: ?UInt256
|
maxFee*: ?UInt256
|
||||||
maxPriorityFee*: ?UInt256
|
maxPriorityFee*: ?UInt256
|
||||||
gasLimit*: ?UInt256
|
gasLimit*: ?UInt256
|
||||||
transactionType*: ?TransactionType
|
transactionType* {.serialize("type").}: ?TransactionType
|
||||||
|
|
||||||
func `$`*(transaction: Transaction): string =
|
func `$`*(transaction: Transaction): string =
|
||||||
result = "("
|
result = "("
|
||||||
|
|
|
@ -1,76 +1,3 @@
|
||||||
import eth/keys
|
import ./signers/wallet
|
||||||
import ./basics
|
|
||||||
import ./provider
|
|
||||||
import ./transaction
|
|
||||||
import ./signer
|
|
||||||
import ./wallet/error
|
|
||||||
import ./wallet/signing
|
|
||||||
|
|
||||||
export keys
|
export wallet
|
||||||
export WalletError
|
|
||||||
export signing
|
|
||||||
|
|
||||||
var rng {.threadvar.}: ref HmacDrbgContext
|
|
||||||
|
|
||||||
proc getRng: ref HmacDrbgContext =
|
|
||||||
if rng.isNil:
|
|
||||||
rng = newRng()
|
|
||||||
rng
|
|
||||||
|
|
||||||
type Wallet* = ref object of Signer
|
|
||||||
privateKey*: PrivateKey
|
|
||||||
publicKey*: PublicKey
|
|
||||||
address*: Address
|
|
||||||
provider*: ?Provider
|
|
||||||
|
|
||||||
proc new*(_: type Wallet, privateKey: PrivateKey): Wallet =
|
|
||||||
let publicKey = privateKey.toPublicKey()
|
|
||||||
let address = Address.init(publicKey.toCanonicalAddress())
|
|
||||||
Wallet(privateKey: privateKey, publicKey: publicKey, address: address)
|
|
||||||
proc new*(_: type Wallet, privateKey: PrivateKey, provider: Provider): Wallet =
|
|
||||||
let wallet = Wallet.new(privateKey)
|
|
||||||
wallet.provider = some provider
|
|
||||||
wallet
|
|
||||||
proc new*(_: type Wallet, privateKey: string): ?!Wallet =
|
|
||||||
let keyResult = PrivateKey.fromHex(privateKey)
|
|
||||||
if keyResult.isErr:
|
|
||||||
return failure newException(WalletError, "invalid key: " & $keyResult.error)
|
|
||||||
success Wallet.new(keyResult.get())
|
|
||||||
proc new*(_: type Wallet, privateKey: string, provider: Provider): ?!Wallet =
|
|
||||||
let wallet = ? Wallet.new(privateKey)
|
|
||||||
wallet.provider = some provider
|
|
||||||
success wallet
|
|
||||||
proc connect*(wallet: Wallet, provider: Provider) =
|
|
||||||
wallet.provider = some provider
|
|
||||||
proc createRandom*(_: type Wallet): Wallet =
|
|
||||||
result = Wallet()
|
|
||||||
result.privateKey = PrivateKey.random(getRng()[])
|
|
||||||
result.publicKey = result.privateKey.toPublicKey()
|
|
||||||
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
|
||||||
proc createRandom*(_: type Wallet, provider: Provider): Wallet =
|
|
||||||
result = Wallet()
|
|
||||||
result.privateKey = PrivateKey.random(getRng()[])
|
|
||||||
result.publicKey = result.privateKey.toPublicKey()
|
|
||||||
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
|
||||||
result.provider = some provider
|
|
||||||
|
|
||||||
method provider*(wallet: Wallet): Provider =
|
|
||||||
without provider =? wallet.provider:
|
|
||||||
raiseWalletError "Wallet has no provider"
|
|
||||||
provider
|
|
||||||
|
|
||||||
method getAddress(wallet: Wallet): Future[Address] {.async.} =
|
|
||||||
return wallet.address
|
|
||||||
|
|
||||||
proc signTransaction*(wallet: Wallet,
|
|
||||||
transaction: Transaction): Future[seq[byte]] {.async.} =
|
|
||||||
if sender =? transaction.sender and sender != wallet.address:
|
|
||||||
raiseWalletError "from address mismatch"
|
|
||||||
|
|
||||||
return wallet.privateKey.sign(transaction)
|
|
||||||
|
|
||||||
method sendTransaction*(wallet: Wallet, transaction: Transaction): Future[TransactionResponse] {.async.} =
|
|
||||||
let signed = await signTransaction(wallet, transaction)
|
|
||||||
if nonce =? transaction.nonce:
|
|
||||||
wallet.updateNonce(nonce)
|
|
||||||
return await provider(wallet).sendTransaction(signed)
|
|
|
@ -1,7 +0,0 @@
|
||||||
import ../basics
|
|
||||||
|
|
||||||
type
|
|
||||||
WalletError* = object of EthersError
|
|
||||||
|
|
||||||
func raiseWalletError*(message: string) =
|
|
||||||
raise newException(WalletError, message)
|
|
|
@ -11,10 +11,15 @@ func new*(_: type MockSigner, provider: Provider): MockSigner =
|
||||||
method provider*(signer: MockSigner): Provider =
|
method provider*(signer: MockSigner): Provider =
|
||||||
signer.provider
|
signer.provider
|
||||||
|
|
||||||
method getAddress*(signer: MockSigner): Future[Address] {.async.} =
|
method getAddress*(
|
||||||
|
signer: MockSigner): Future[Address]
|
||||||
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
return signer.address
|
return signer.address
|
||||||
|
|
||||||
method sendTransaction*(signer: MockSigner,
|
method sendTransaction*(
|
||||||
transaction: Transaction):
|
signer: MockSigner,
|
||||||
Future[TransactionResponse] {.async.} =
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
|
{.async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
signer.transactions.add(transaction)
|
signer.transactions.add(transaction)
|
||||||
|
|
|
@ -2,6 +2,9 @@ import std/strutils
|
||||||
import std/unittest
|
import std/unittest
|
||||||
import pkg/ethers/provider
|
import pkg/ethers/provider
|
||||||
import pkg/ethers/providers/jsonrpc/conversions
|
import pkg/ethers/providers/jsonrpc/conversions
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/questionable/results
|
||||||
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
|
|
||||||
func flatten(s: string): string =
|
func flatten(s: string): string =
|
||||||
|
@ -17,14 +20,14 @@ suite "JSON Conversions":
|
||||||
"timestamp":"0x6285c293"
|
"timestamp":"0x6285c293"
|
||||||
}
|
}
|
||||||
|
|
||||||
var blk = Block.fromJson(json)
|
let blk1 = !Block.fromJson(json)
|
||||||
check blk.number.isNone
|
check blk1.number.isNone
|
||||||
|
|
||||||
json["number"] = newJString("")
|
json["number"] = newJString("")
|
||||||
|
|
||||||
blk = Block.fromJson(json)
|
let blk2 = !Block.fromJson(json)
|
||||||
check blk.number.isSome
|
check blk2.number.isSome
|
||||||
check blk.number.get.isZero
|
check blk2.number.get.isZero
|
||||||
|
|
||||||
test "missing block hash in Block isNone":
|
test "missing block hash in Block isNone":
|
||||||
|
|
||||||
|
@ -37,7 +40,8 @@ suite "JSON Conversions":
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var blk = Block.fromJson(blkJson["result"])
|
without blk =? Block.fromJson(blkJson["result"]):
|
||||||
|
fail
|
||||||
check blk.hash.isNone
|
check blk.hash.isNone
|
||||||
|
|
||||||
test "missing block number in TransactionReceipt isNone":
|
test "missing block number in TransactionReceipt isNone":
|
||||||
|
@ -67,13 +71,15 @@ suite "JSON Conversions":
|
||||||
"type": "0x0"
|
"type": "0x0"
|
||||||
}
|
}
|
||||||
|
|
||||||
var receipt = TransactionReceipt.fromJson(json)
|
without receipt1 =? TransactionReceipt.fromJson(json):
|
||||||
check receipt.blockNumber.isNone
|
fail
|
||||||
|
check receipt1.blockNumber.isNone
|
||||||
|
|
||||||
json["blockNumber"] = newJString("")
|
json["blockNumber"] = newJString("")
|
||||||
receipt = TransactionReceipt.fromJson(json)
|
without receipt2 =? TransactionReceipt.fromJson(json):
|
||||||
check receipt.blockNumber.isSome
|
fail
|
||||||
check receipt.blockNumber.get.isZero
|
check receipt2.blockNumber.isSome
|
||||||
|
check receipt2.blockNumber.get.isZero
|
||||||
|
|
||||||
test "missing block hash in TransactionReceipt isNone":
|
test "missing block hash in TransactionReceipt isNone":
|
||||||
let json = %*{
|
let json = %*{
|
||||||
|
@ -102,33 +108,10 @@ suite "JSON Conversions":
|
||||||
"type": "0x0"
|
"type": "0x0"
|
||||||
}
|
}
|
||||||
|
|
||||||
let receipt = TransactionReceipt.fromJson(json)
|
without receipt =? TransactionReceipt.fromJson(json):
|
||||||
|
fail
|
||||||
check receipt.blockHash.isNone
|
check receipt.blockHash.isNone
|
||||||
|
|
||||||
test "newHeads subcription raises exception when deserializing to Log":
|
|
||||||
let json = """{
|
|
||||||
"parentHash":"0xd68d4d0f29307df51e1284fc8a13595ae700ef0f1128830a69e6854381363d42",
|
|
||||||
"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
|
||||||
"miner":"0x0000000000000000000000000000000000000000",
|
|
||||||
"stateRoot":"0x1f6f2d05de35bbfd50213be96ddf960d62b978b472c55d6ac223cd648cbbbbb0",
|
|
||||||
"transactionsRoot":"0xb9bb8a26abe091bb628ab2b6585c5af151aeb3984f4ba47a3c65d438283e069d",
|
|
||||||
"receiptsRoot":"0x33f229b7133e1ba3fb524b8af22d8184ca10b2da5bb170092a219c61ca023c1d",
|
|
||||||
"logsBloom":"0x00000000000000000000000000000000000000000020000000000002000000000000000000000000000000000000000000000000000008080000100200200000000000000000000000000008000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000010000040000000000100000000000800000000000000000000000000000000020000000000020000000000000000000000000000040000008000000000000000000020000000000002000000000000000000000000000000000000000000000000000001000010000000000000000020002000000020000000000000008002000000000000",
|
|
||||||
"difficulty":"0x2",
|
|
||||||
"number":"0x21d",
|
|
||||||
"gasLimit":"0x1c1b59a7",
|
|
||||||
"gasUsed":"0xda41b",
|
|
||||||
"timestamp":"0x6509410e",
|
|
||||||
"extraData":"0xd883010b05846765746888676f312e32302e32856c696e7578000000000000007102a27d75709b90ca9eb23cdaaccf4fc2d571d710f3bc5a7dc874f43af116a93ff832576a53c16f0d0aa1cd9e9a1dc0a60126c4d420f72b0866fc96ba6664f601",
|
|
||||||
"mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"nonce":"0x0000000000000000",
|
|
||||||
"baseFeePerGas":"0x7",
|
|
||||||
"withdrawalsRoot":null,
|
|
||||||
"hash":"0x64066c7150c660e5357c4b6b02d836c10353dfa8edb32c805fca9367fd29c6e7"
|
|
||||||
}"""
|
|
||||||
expect ValueError:
|
|
||||||
discard Log.fromJson(parseJson(json))
|
|
||||||
|
|
||||||
test "correctly deserializes PastTransaction":
|
test "correctly deserializes PastTransaction":
|
||||||
let json = %*{
|
let json = %*{
|
||||||
"blockHash":"0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922",
|
"blockHash":"0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922",
|
||||||
|
@ -149,7 +132,8 @@ suite "JSON Conversions":
|
||||||
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2"
|
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2"
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx = PastTransaction.fromJson(json)
|
without tx =? PastTransaction.fromJson(json):
|
||||||
|
fail
|
||||||
check tx.blockHash == BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922"))
|
check tx.blockHash == BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922"))
|
||||||
check tx.blockNumber == 0x22e.u256
|
check tx.blockNumber == 0x22e.u256
|
||||||
check tx.sender == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get
|
check tx.sender == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get
|
||||||
|
@ -198,12 +182,12 @@ suite "JSON Conversions":
|
||||||
"nonce":"0x3",
|
"nonce":"0x3",
|
||||||
"to":"0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e",
|
"to":"0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e",
|
||||||
"transactionIndex":"0x3",
|
"transactionIndex":"0x3",
|
||||||
|
"type":"0x0",
|
||||||
|
"chainId":"0xc0de4",
|
||||||
"value":"0x0",
|
"value":"0x0",
|
||||||
"v":"0x181bec",
|
"v":"0x181bec",
|
||||||
"r":"0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a",
|
"r":"0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a",
|
||||||
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2",
|
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2"
|
||||||
"type":"0x0",
|
|
||||||
"chainId":"0xc0de4"
|
|
||||||
}""".flatten
|
}""".flatten
|
||||||
check $(%tx) == expected
|
check $(%tx) == expected
|
||||||
|
|
||||||
|
@ -227,10 +211,12 @@ suite "JSON Conversions":
|
||||||
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2"
|
"s":"0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2"
|
||||||
}
|
}
|
||||||
|
|
||||||
let past = PastTransaction.fromJson(json)
|
without past =? PastTransaction.fromJson(json):
|
||||||
|
fail
|
||||||
check %past.toTransaction == %*{
|
check %past.toTransaction == %*{
|
||||||
"to": !Address.init("0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e"),
|
"to": !Address.init("0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e"),
|
||||||
"data": hexToSeqByte("0x6368a471d26ff5c7f835c1a8203235e88846ce1a196d6e79df0eaedd1b8ed3deec2ae5c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000"),
|
"data": hexToSeqByte("0x6368a471d26ff5c7f835c1a8203235e88846ce1a196d6e79df0eaedd1b8ed3deec2ae5c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
"value": "0x0",
|
||||||
"from": !Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34"),
|
"from": !Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34"),
|
||||||
"nonce": 0x3.u256,
|
"nonce": 0x3.u256,
|
||||||
"chainId": 0xc0de4.u256,
|
"chainId": 0xc0de4.u256,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import std/json
|
|
||||||
import pkg/asynctest
|
import pkg/asynctest
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
@ -97,5 +96,5 @@ for url in ["ws://localhost:8545", "http://localhost:8545"]:
|
||||||
discard await provider.getBlock(BlockTag.latest)
|
discard await provider.getBlock(BlockTag.latest)
|
||||||
expect JsonRpcProviderError:
|
expect JsonRpcProviderError:
|
||||||
discard await provider.subscribe(proc(_: Block) = discard)
|
discard await provider.subscribe(proc(_: Block) = discard)
|
||||||
expect JsonRpcProviderError:
|
expect JsonRpcSignerError:
|
||||||
discard await provider.getSigner().sendTransaction(Transaction.example)
|
discard await provider.getSigner().sendTransaction(Transaction.example)
|
||||||
|
|
|
@ -64,6 +64,7 @@ suite "Web socket subscriptions":
|
||||||
client = newRpcWebSocketClient()
|
client = newRpcWebSocketClient()
|
||||||
await client.connect("ws://localhost:8545")
|
await client.connect("ws://localhost:8545")
|
||||||
subscriptions = JsonRpcSubscriptions.new(client)
|
subscriptions = JsonRpcSubscriptions.new(client)
|
||||||
|
subscriptions.start()
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
await subscriptions.close()
|
await subscriptions.close()
|
||||||
|
@ -81,6 +82,7 @@ suite "HTTP polling subscriptions":
|
||||||
await client.connect("http://localhost:8545")
|
await client.connect("http://localhost:8545")
|
||||||
subscriptions = JsonRpcSubscriptions.new(client,
|
subscriptions = JsonRpcSubscriptions.new(client,
|
||||||
pollingInterval = 100.millis)
|
pollingInterval = 100.millis)
|
||||||
|
subscriptions.start()
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
await subscriptions.close()
|
await subscriptions.close()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import pkg/asynctest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
import pkg/serde
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import pkg/asynctest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
import pkg/serde
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
@ -3,6 +3,7 @@ import pkg/asynctest
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/ethers/testing
|
import pkg/ethers/testing
|
||||||
|
import pkg/serde
|
||||||
import ./helpers
|
import ./helpers
|
||||||
|
|
||||||
suite "Testing helpers":
|
suite "Testing helpers":
|
||||||
|
@ -31,12 +32,15 @@ suite "Testing helpers":
|
||||||
test "reverts only checks ProviderErrors, EstimateGasErrors":
|
test "reverts only checks ProviderErrors, EstimateGasErrors":
|
||||||
proc callProviderError() {.async.} =
|
proc callProviderError() {.async.} =
|
||||||
raise newException(ProviderError, "test")
|
raise newException(ProviderError, "test")
|
||||||
|
proc callSignerError() {.async.} =
|
||||||
|
raise newException(SignerError, "test")
|
||||||
proc callEstimateGasError() {.async.} =
|
proc callEstimateGasError() {.async.} =
|
||||||
raise newException(EstimateGasError, "test")
|
raise newException(EstimateGasError, "test")
|
||||||
proc callEthersError() {.async.} =
|
proc callEthersError() {.async.} =
|
||||||
raise newException(EthersError, "test")
|
raise newException(EthersError, "test")
|
||||||
|
|
||||||
check await callProviderError().reverts()
|
check await callProviderError().reverts()
|
||||||
|
check await callSignerError().reverts()
|
||||||
check await callEstimateGasError().reverts()
|
check await callEstimateGasError().reverts()
|
||||||
expect EthersError:
|
expect EthersError:
|
||||||
check await callEthersError().reverts()
|
check await callEthersError().reverts()
|
||||||
|
@ -44,12 +48,15 @@ suite "Testing helpers":
|
||||||
test "reverts with reason only checks ProviderErrors, EstimateGasErrors":
|
test "reverts with reason only checks ProviderErrors, EstimateGasErrors":
|
||||||
proc callProviderError() {.async.} =
|
proc callProviderError() {.async.} =
|
||||||
raise newException(ProviderError, revertReason)
|
raise newException(ProviderError, revertReason)
|
||||||
|
proc callSignerError() {.async.} =
|
||||||
|
raise newException(SignerError, revertReason)
|
||||||
proc callEstimateGasError() {.async.} =
|
proc callEstimateGasError() {.async.} =
|
||||||
raise newException(EstimateGasError, revertReason)
|
raise newException(EstimateGasError, revertReason)
|
||||||
proc callEthersError() {.async.} =
|
proc callEthersError() {.async.} =
|
||||||
raise newException(EthersError, revertReason)
|
raise newException(EthersError, revertReason)
|
||||||
|
|
||||||
check await callProviderError().reverts(revertReason)
|
check await callProviderError().reverts(revertReason)
|
||||||
|
check await callSignerError().reverts(revertReason)
|
||||||
check await callEstimateGasError().reverts(revertReason)
|
check await callEstimateGasError().reverts(revertReason)
|
||||||
expect EthersError:
|
expect EthersError:
|
||||||
check await callEthersError().reverts(revertReason)
|
check await callEthersError().reverts(revertReason)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pkg/asynctest
|
import pkg/asynctest
|
||||||
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../ethers
|
import ../ethers
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue