diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index c5fe90f..6bf01ee 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -240,6 +240,7 @@ method signMessage*(signer: JsonRpcSigner, method sendTransaction*(signer: JsonRpcSigner, transaction: Transaction): Future[TransactionResponse] {.async.} = convertError: + signer.updateNonce(transaction.nonce) let client = await signer.provider.client hash = await client.eth_sendTransaction(transaction) diff --git a/ethers/signer.nim b/ethers/signer.nim index 4208c6d..e22b118 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -3,7 +3,10 @@ import ./provider export basics -type Signer* = ref object of RootObj +type + Signer* = ref object of RootObj + lastSeenNonce: ?UInt256 + type SignerError* = object of EthersError template raiseSignerError(message: string) = @@ -41,6 +44,26 @@ method estimateGas*(signer: Signer, method getChainId*(signer: Signer): Future[UInt256] {.base, gcsafe.} = signer.provider.getChainId() +method getNonce(signer: Signer): Future[UInt256] {.base, gcsafe, async.} = + var nonce = await signer.getTransactionCount(BlockTag.pending) + + if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce: + nonce = (lastSeen + 1.u256) + signer.lastSeenNonce = some nonce + + return nonce + +method updateNonce*(signer: Signer, nonce: ?UInt256) {.base, gcsafe.} = + without nonce =? nonce: + return + + without lastSeen =? signer.lastSeenNonce: + signer.lastSeenNonce = some nonce + return + + if nonce > lastSeen: + signer.lastSeenNonce = some nonce + method populateTransaction*(signer: Signer, transaction: Transaction): Future[Transaction] {.base, async.} = @@ -55,7 +78,7 @@ method populateTransaction*(signer: Signer, if transaction.sender.isNone: populated.sender = some(await signer.getAddress()) if transaction.nonce.isNone: - populated.nonce = some(await signer.getTransactionCount(BlockTag.pending)) + populated.nonce = some(await signer.getNonce()) if transaction.chainId.isNone: populated.chainId = some(await signer.getChainId()) if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone): diff --git a/ethers/transaction.nim b/ethers/transaction.nim index abd1e3f..92652af 100644 --- a/ethers/transaction.nim +++ b/ethers/transaction.nim @@ -21,11 +21,11 @@ func `$`*(transaction: Transaction): string = result &= "value: " & $transaction.value & ", " result &= "data: 0x" & $transaction.data.toHex if nonce =? transaction.nonce: - result &= ", nonce: 0x" & $nonce.toHex + result &= ", nonce: " & $nonce if chainId =? transaction.chainId: result &= ", chainId: " & $chainId if gasPrice =? transaction.gasPrice: - result &= ", gasPrice: 0x" & $gasPrice.toHex + result &= ", gasPrice: " & $gasPrice if gasLimit =? transaction.gasLimit: - result &= ", gasLimit: 0x" & $gasLimit.toHex + result &= ", gasLimit: " & $gasLimit result &= ")" diff --git a/ethers/wallet.nim b/ethers/wallet.nim index 317dde0..22e13e5 100644 --- a/ethers/wallet.nim +++ b/ethers/wallet.nim @@ -70,4 +70,5 @@ proc signTransaction*(wallet: Wallet, method sendTransaction*(wallet: Wallet, transaction: Transaction): Future[TransactionResponse] {.async.} = let signed = await signTransaction(wallet, transaction) + wallet.updateNonce(transaction.nonce) return await provider(wallet).sendTransaction(signed) diff --git a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim index 2789572..34d78ea 100644 --- a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim +++ b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim @@ -82,3 +82,24 @@ suite "JsonRpcSigner": transaction.chainId = 0xdeadbeef.u256.some expect SignerError: discard await signer.populateTransaction(transaction) + + test "concurrent populate calls increment nonce": + let signer = provider.getSigner() + let count = await signer.getTransactionCount(BlockTag.pending) + var transaction1 = Transaction.example + var transaction2 = Transaction.example + var transaction3 = Transaction.example + + let populated = await allFinished( + signer.populateTransaction(transaction1), + signer.populateTransaction(transaction2), + signer.populateTransaction(transaction3) + ) + + transaction1 = await populated[0] + transaction2 = await populated[1] + transaction3 = await populated[2] + + check !transaction1.nonce == count + check !transaction2.nonce == count + 1.u256 + check !transaction3.nonce == count + 2.u256