Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ JSON-RPC provider is supported:
import ethers
import chronos

let provider = JsonRpcProvider.new("ws://localhost:8545")
let provider = await JsonRpcProvider.connect("ws://localhost:8545")
let accounts = await provider.listAccounts()
```

Expand Down Expand Up @@ -137,11 +137,8 @@ You can now subscribe to Transfer events by calling `subscribe` on the contract
instance.

```nim
proc handleTransfer(transferResult: ?!Transfer) =
if transferResult.isOk:
echo "received transfer: ", transferResult.value
else:
echo "error during transfer: ", transferResult.error.msg
proc handleTransfer(transferResult: Transfer) =
echo "received transfer: ", transferResult.value

let subscription = await token.subscribe(Transfer, handleTransfer)
```
Expand Down Expand Up @@ -204,13 +201,6 @@ This library ships with some optional modules that provides convenience utilitie

- `ethers/erc20` module provides you with ERC20 token implementation and its events

Hardhat websockets workaround
---------

If you're working with Hardhat, you might encounter an issue where [websocket subscriptions stop working after 5 minutes](https://github.com/NomicFoundation/hardhat/issues/2053).

This library provides a workaround using the compile time `ws_resubscribe` symbol. When this symbol is defined and set to a value greater than 0, websocket subscriptions will automatically resubscribe after the amount of time (in seconds) specified. The recommended value is 240 seconds (4 minutes), eg `--define:ws_resubscribe=240`.

Contribution
------------

Expand Down
8 changes: 0 additions & 8 deletions config.nims
Original file line number Diff line number Diff line change
@@ -1,10 +1,2 @@
--styleCheck:usages
--styleCheck:error

# begin Nimble config (version 1)
when fileExists("nimble.paths"):
include "nimble.paths"
# end Nimble config

when (NimMajor, NimMinor) >= (2, 0):
--mm:refc
10 changes: 3 additions & 7 deletions ethers/contracts/filters.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ./contract
import ./events
import ./fields

type EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
type EventHandler*[E: Event] = proc(event: E) {.gcsafe, raises:[].}

proc subscribe*[E: Event](contract: Contract,
_: type E,
Expand All @@ -16,13 +16,9 @@ proc subscribe*[E: Event](contract: Contract,
let topic = topic($E, E.fieldTypes).toArray
let filter = EventFilter(address: contract.address, topics: @[topic])

proc logHandler(logResult: ?!Log) {.raises: [].} =
without log =? logResult, error:
handler(failure(E, error))
return

proc logHandler(log: Log) {.raises: [].} =
if event =? E.decode(log.data, log.topics):
handler(success(event))
handler(event)

contract.provider.subscribe(filter, logHandler)

Expand Down
1 change: 0 additions & 1 deletion ethers/errors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ type
SolidityError* = object of EthersError
ContractError* = object of EthersError
SignerError* = object of EthersError
SubscriptionError* = object of EthersError
ProviderError* = object of EthersError
data*: ?seq[byte]

Expand Down
88 changes: 31 additions & 57 deletions ethers/provider.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ type
FilterByBlockHash* {.serialize.} = ref object of EventFilter
blockHash*: BlockHash
Log* {.serialize.} = object
blockNumber*: UInt256
blockNumber*: BlockNumber
data*: seq[byte]
logIndex*: UInt256
removed*: bool
address*: Address
topics*: seq[Topic]
TransactionHash* = array[32, byte]
BlockHash* = array[32, byte]
Expand All @@ -51,22 +52,24 @@ type
blockHash*: ?BlockHash
transactionHash*: TransactionHash
logs*: seq[Log]
blockNumber*: ?UInt256
blockNumber*: ?BlockNumber
cumulativeGasUsed*: UInt256
effectiveGasPrice*: ?UInt256
status*: TransactionStatus
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
LogHandler* = proc(log: ?!Log) {.gcsafe, raises:[].}
BlockHandler* = proc(blck: ?!Block) {.gcsafe, raises:[].}
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
Topic* = array[32, byte]
Block* {.serialize.} = object
number*: ?UInt256
number*: ?BlockNumber
timestamp*: UInt256
hash*: ?BlockHash
baseFeePerGas* : ?UInt256
logsBloom*: ?StUint[2048]
BlockNumber* = UInt256
PastTransaction* {.serialize.} = object
blockHash*: BlockHash
blockNumber*: UInt256
blockNumber*: BlockNumber
sender* {.serialize("from"), deserialize("from").}: Address
gas*: UInt256
gasPrice*: UInt256
Expand Down Expand Up @@ -222,7 +225,7 @@ proc confirm*(
tx: TransactionResponse,
confirmations = EthersDefaultConfirmations,
timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt]
{.async: (raises: [CancelledError, ProviderError, SubscriptionError, EthersError]).} =
{.async: (raises: [CancelledError, ProviderError, EthersError]).} =

## Waits for a transaction to be mined and for the specified number of blocks
## to pass since it was mined (confirmations). The number of confirmations
Expand All @@ -232,69 +235,40 @@ proc confirm*(

assert confirmations > 0

var blockNumber: UInt256

## We need initialized succesfull Result, because the first iteration of the `while` loop
## bellow is triggered "manually" by calling `await updateBlockNumber` and not by block
## subscription. If left uninitialized then the Result is in error state and error is raised.
## This result is not used for block value, but for block subscription errors.
var blockSubscriptionResult: ?!Block = success(Block(number: UInt256.none, timestamp: 0.u256, hash: BlockHash.none))
var blockNumber = await tx.provider.getBlockNumber()
let blockEvent = newAsyncEvent()
blockEvent.fire()

proc updateBlockNumber {.async: (raises: []).} =
try:
let number = await tx.provider.getBlockNumber()
if number > blockNumber:
blockNumber = number
blockEvent.fire()
except ProviderError, CancelledError:
# there's nothing we can do here
discard

proc onBlock(blckResult: ?!Block) =
blockSubscriptionResult = blckResult

if blckResult.isErr:
proc onBlock(blck: Block) =
if number =? blck.number:
blockNumber = number
blockEvent.fire()
return

# ignore block parameter; hardhat may call this with pending blocks
asyncSpawn updateBlockNumber()

await updateBlockNumber()
let subscription = await tx.provider.subscribe(onBlock)

let finish = blockNumber + timeout.u256
var receipt: ?TransactionReceipt

while true:
await blockEvent.wait()
blockEvent.clear()

if blockSubscriptionResult.isErr:
let error = blockSubscriptionResult.error()
try:
var receipt: ?TransactionReceipt

if error of SubscriptionError:
raise (ref SubscriptionError)(error)
elif error of CancelledError:
raise (ref CancelledError)(error)
else:
raise error.toErr(ProviderError)
while true:
await blockEvent.wait()
blockEvent.clear()

if blockNumber >= finish:
await subscription.unsubscribe()
raise newException(EthersError, "tx not mined before timeout")
if blockNumber >= finish:
raise newException(EthersError, "tx not mined before timeout")

if receipt.?blockNumber.isNone:
receipt = await tx.provider.getTransactionReceipt(tx.hash)
if receipt.?blockNumber.isNone:
receipt = await tx.provider.getTransactionReceipt(tx.hash)

without receipt =? receipt and txBlockNumber =? receipt.blockNumber:
continue
without receipt =? receipt and txBlockNumber =? receipt.blockNumber:
continue

if txBlockNumber + confirmations.u256 <= blockNumber + 1:
await subscription.unsubscribe()
await tx.provider.ensureSuccess(receipt)
return receipt
if txBlockNumber + confirmations.u256 <= blockNumber + 1:
await tx.provider.ensureSuccess(receipt)
return receipt
finally:
await subscription.unsubscribe()

proc confirm*(
tx: Future[TransactionResponse],
Expand Down
Loading