diff --git a/ethers/provider.nim b/ethers/provider.nim index e6c7624..ade5810 100644 --- a/ethers/provider.nim +++ b/ethers/provider.nim @@ -95,3 +95,114 @@ method subscribe*(provider: Provider, method unsubscribe*(subscription: Subscription) {.base, async.} = doAssert false, "not implemented" + +import std/options # TODO + +# Removed from `confirm` closure and exported so it can be tested. +# Likely there is a better way +func confirmations*(receiptBlk, atBlk: UInt256): UInt256 = + ## Calculates the number of confirmations between two blocks + if atBlk < receiptBlk: + return 0.u256 + else: + return (atBlk - receiptBlk) + 1 # add 1 for current block + +# Removed from `confirm` closure and exported so it can be tested. +# Likely there is a better way +func hasBeenMined*(receipt: ?TransactionReceipt, + atBlock: UInt256, + wantedConfirms: int): bool = + ## Returns true if the transaction receipt has been returned from the node + ## with a valid block number and block hash and the specified number of + ## blocks have passed since the tx was mined (confirmations) + + if receipt.isSome and + receipt.get.blockNumber.isSome and + receipt.get.blockNumber.get > 0 and + # from ethers.js: "geth-etc" returns receipts before they are ready + receipt.get.blockHash.isSome: + + let receiptBlock = receipt.get.blockNumber.get + return receiptBlock.confirmations(atBlock) >= wantedConfirms.u256 + + return false + +proc confirm*(tx: TransactionResponse, + wantedConfirms: Positive = EthersDefaultConfirmations, + timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): + Future[TransactionReceipt] + {.async, upraises: [EthersError].} = # raises for clarity + ## Waits for a transaction to be mined and for the specified number of blocks + ## to pass since it was mined (confirmations). + ## A timeout, in blocks, can be specified that will raise an error if too many + ## blocks have passed without the tx having been mined. + + var subscription: Subscription + let + provider = tx.provider + retFut = newFuture[TransactionReceipt]("wait") + + # used to check for block timeouts + let startBlock = await provider.getBlockNumber() + + proc newBlock(blk: Block) {.async.} = + ## subscription callback, called every time a new block event is sent from + ## the node + + # if ethereum node doesn't include blockNumber in the event + without blkNum =? blk.number: + return + + let receipt = await provider.getTransactionReceipt(tx.hash) + if receipt.hasBeenMined(blkNum, wantedConfirms): + # fire and forget + discard subscription.unsubscribe() + if not retFut.finished: + retFut.complete(receipt.get) + + elif timeoutInBlocks > 0: + let blocksPassed = (blkNum - startBlock) + 1 + if blocksPassed >= timeoutInBlocks.u256: + discard subscription.unsubscribe() + if not retFut.finished: + let message = + "Transaction was not mined in " & $timeoutInBlocks & " blocks" + retFut.fail(newException(EthersError, message)) + + # If our tx is already mined, return the receipt. Otherwise, check each + # new block to see if the tx has been mined + let receipt = await provider.getTransactionReceipt(tx.hash) + if receipt.hasBeenMined(startBlock, wantedConfirms): + return receipt.get + else: + subscription = await provider.subscribe(newBlock) + return (await retFut) + +proc confirm*(tx: Future[TransactionResponse], + wantedConfirms: Positive = EthersDefaultConfirmations, + timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): + Future[TransactionReceipt] {.async.} = + ## Convenience method that allows wait to be chained to a sendTransaction + ## call, eg: + ## `await signer.sendTransaction(populated).confirm(3)` + + let txResp = await tx + return await txResp.confirm(wantedConfirms, timeoutInBlocks) + +proc confirm*(tx: Future[?TransactionResponse], + wantedConfirms: Positive = EthersDefaultConfirmations, + timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): + Future[TransactionReceipt] {.async.} = + ## Convenience method that allows wait to be chained to a contract + ## transaction, eg: + ## `await token.connect(signer0) + ## .mint(accounts[1], 100.u256) + ## .confirm(3)` + + without txResp =? (await tx): + raise newException( + EthersError, + "Transaction hash required. Possibly was a call instead of a send?" + ) + + return await txResp.confirm(wantedConfirms, timeoutInBlocks) diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index ebc5a96..727e653 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -197,117 +197,3 @@ method sendTransaction*(signer: JsonRpcSigner, hash = await client.eth_sendTransaction(transaction) return TransactionResponse(hash: hash, provider: signer.provider) - - -# Removed from `confirm` closure and exported so it can be tested. -# Likely there is a better way -func confirmations*(receiptBlk, atBlk: UInt256): UInt256 = - ## Calculates the number of confirmations between two blocks - if atBlk < receiptBlk: - return 0.u256 - else: - return (atBlk - receiptBlk) + 1 # add 1 for current block - -# Removed from `confirm` closure and exported so it can be tested. -# Likely there is a better way -func hasBeenMined*(receipt: ?TransactionReceipt, - atBlock: UInt256, - wantedConfirms: int): bool = - ## Returns true if the transaction receipt has been returned from the node - ## with a valid block number and block hash and the specified number of - ## blocks have passed since the tx was mined (confirmations) - - if receipt.isSome and - receipt.get.blockNumber.isSome and - receipt.get.blockNumber.get > 0 and - # from ethers.js: "geth-etc" returns receipts before they are ready - receipt.get.blockHash.isSome: - - let receiptBlock = receipt.get.blockNumber.get - return receiptBlock.confirmations(atBlock) >= wantedConfirms.u256 - - return false - -proc confirm*(tx: TransactionResponse, - wantedConfirms: Positive = EthersDefaultConfirmations, - timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): - Future[TransactionReceipt] - {.async, upraises: [EthersError].} = # raises for clarity - ## Waits for a transaction to be mined and for the specified number of blocks - ## to pass since it was mined (confirmations). - ## A timeout, in blocks, can be specified that will raise a - ## JsonRpcProviderError if too many blocks have passed without the tx - ## having been mined. - ## Note: this method requires that the JsonRpcProvider client connects - ## using RpcWebSocketClient, otherwise it will raise a Defect. - - var subscription: JsonRpcSubscription - let - provider = JsonRpcProvider(tx.provider) - retFut = newFuture[TransactionReceipt]("wait") - - # used to check for block timeouts - let startBlock = await provider.getBlockNumber() - - proc newBlock(blk: Block) {.async.} = - ## subscription callback, called every time a new block event is sent from - ## the node - - # if ethereum node doesn't include blockNumber in the event - without blkNum =? blk.number: - return - - let receipt = await provider.getTransactionReceipt(tx.hash) - if receipt.hasBeenMined(blkNum, wantedConfirms): - # fire and forget - discard subscription.unsubscribe() - if not retFut.finished: - retFut.complete(receipt.get) - - elif timeoutInBlocks > 0: - let blocksPassed = (blkNum - startBlock) + 1 - if blocksPassed >= timeoutInBlocks.u256: - discard subscription.unsubscribe() - if not retFut.finished: - let message = - "Transaction was not mined in " & $timeoutInBlocks & " blocks" - retFut.fail(newException(EthersError, message)) - - # If our tx is already mined, return the receipt. Otherwise, check each - # new block to see if the tx has been mined - let receipt = await provider.getTransactionReceipt(tx.hash) - if receipt.hasBeenMined(startBlock, wantedConfirms): - return receipt.get - else: - let sub = await provider.subscribe(newBlock) - subscription = JsonRpcSubscription(sub) - return (await retFut) - -proc confirm*(tx: Future[TransactionResponse], - wantedConfirms: Positive = EthersDefaultConfirmations, - timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): - Future[TransactionReceipt] {.async.} = - ## Convenience method that allows wait to be chained to a sendTransaction - ## call, eg: - ## `await signer.sendTransaction(populated).confirm(3)` - - let txResp = await tx - return await txResp.confirm(wantedConfirms, timeoutInBlocks) - -proc confirm*(tx: Future[?TransactionResponse], - wantedConfirms: Positive = EthersDefaultConfirmations, - timeoutInBlocks: Natural = EthersReceiptTimeoutBlks): - Future[TransactionReceipt] {.async.} = - ## Convenience method that allows wait to be chained to a contract - ## transaction, eg: - ## `await token.connect(signer0) - ## .mint(accounts[1], 100.u256) - ## .confirm(3)` - - without txResp =? (await tx): - raise newException( - EthersError, - "Transaction hash required. Possibly was a call instead of a send?" - ) - - return await txResp.confirm(wantedConfirms, timeoutInBlocks)