@@ -6,11 +6,25 @@ export basics
66type
77 Signer * = ref object of RootObj
88 lastSeenNonce: ? UInt256
9+ populateLock: AsyncLock
910
10- type SignerError * = object of EthersError
11-
12- template raiseSignerError (message: string ) =
13- raise newException (SignerError , message)
11+ type
12+ SignerError * = object of EthersError
13+ EstimateGasError * = object of SignerError
14+ transaction* : Transaction
15+
16+ template raiseSignerError (message: string , parent: ref ProviderError = nil ) =
17+ raise newException (SignerError , message, parent)
18+
19+ proc raiseEstimateGasError (
20+ transaction: Transaction ,
21+ parent: ref ProviderError = nil
22+ ) =
23+ let e = (ref EstimateGasError )(
24+ msg: " Estimate gas failed" ,
25+ transaction: transaction,
26+ parent: parent)
27+ raise e
1428
1529method provider * (signer: Signer ): Provider {.base , gcsafe .} =
1630 doAssert false , " not implemented"
@@ -39,23 +53,27 @@ method estimateGas*(signer: Signer,
3953 transaction: Transaction ): Future [UInt256 ] {.base , async .} =
4054 var transaction = transaction
4155 transaction.sender = some (await signer.getAddress)
42- return await signer.provider.estimateGas (transaction)
56+ try :
57+ return await signer.provider.estimateGas (transaction)
58+ except ProviderError as e:
59+ raiseEstimateGasError transaction, e
4360
4461method getChainId * (signer: Signer ): Future [UInt256 ] {.base , gcsafe .} =
4562 signer.provider.getChainId ()
4663
4764method getNonce (signer: Signer ): Future [UInt256 ] {.base , gcsafe , async .} =
4865 var nonce = await signer.getTransactionCount (BlockTag .pending)
49-
66+
5067 if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce:
5168 nonce = (lastSeen + 1 .u256)
5269 signer.lastSeenNonce = some nonce
53-
70+
5471 return nonce
5572
56- method updateNonce * (signer: Signer , nonce: ? UInt256 ) {.base , gcsafe .} =
57- without nonce =? nonce:
58- return
73+ method updateNonce * (
74+ signer: Signer ,
75+ nonce: UInt256
76+ ) {.base , gcsafe .} =
5977
6078 without lastSeen =? signer.lastSeenNonce:
6179 signer.lastSeenNonce = some nonce
@@ -64,6 +82,10 @@ method updateNonce*(signer: Signer, nonce: ?UInt256) {.base, gcsafe.} =
6482 if nonce > lastSeen:
6583 signer.lastSeenNonce = some nonce
6684
85+ method decreaseNonce * (signer: Signer ) {.base , gcsafe .} =
86+ if lastSeen =? signer.lastSeenNonce and lastSeen > 0 :
87+ signer.lastSeenNonce = some lastSeen - 1
88+
6789method populateTransaction * (signer: Signer ,
6890 transaction: Transaction ):
6991 Future [Transaction ] {.base , async .} =
@@ -73,17 +95,55 @@ method populateTransaction*(signer: Signer,
7395 if chainId =? transaction.chainId and chainId != await signer.getChainId ():
7496 raiseSignerError (" chain id mismatch" )
7597
98+ if signer.populateLock.isNil:
99+ signer.populateLock = newAsyncLock ()
100+
101+ await signer.populateLock.acquire ()
102+
76103 var populated = transaction
77104
78105 if transaction.sender.isNone:
79106 populated.sender = some (await signer.getAddress ())
80- if transaction.nonce.isNone:
81- populated.nonce = some (await signer.getNonce ())
82107 if transaction.chainId.isNone:
83108 populated.chainId = some (await signer.getChainId ())
84- if transaction.gasPrice.isNone and (transaction .maxFee.isNone or transaction .maxPriorityFee.isNone):
109+ if transaction.gasPrice.isNone and (populated .maxFee.isNone or populated .maxPriorityFee.isNone):
85110 populated.gasPrice = some (await signer.getGasPrice ())
86- if transaction.gasLimit.isNone:
87- populated.gasLimit = some (await signer.estimateGas (populated))
111+
112+ if transaction.nonce.isNone and transaction.gasLimit.isNone:
113+ # when both nonce and gasLimit are not populated, we must ensure getNonce is
114+ # followed by an estimateGas so we can determine if there was an error. If
115+ # there is an error, the nonce must be deprecated to prevent nonce gaps and
116+ # stuck transactions
117+ try :
118+ populated.nonce = some (await signer.getNonce ())
119+ populated.gasLimit = some (await signer.estimateGas (populated))
120+ except ProviderError , EstimateGasError :
121+ let e = getCurrentException ()
122+ signer.decreaseNonce ()
123+ raise e
124+ finally :
125+ signer.populateLock.release ()
126+
127+ else :
128+ if transaction.nonce.isNone:
129+ populated.nonce = some (await signer.getNonce ())
130+ if transaction.gasLimit.isNone:
131+ populated.gasLimit = some (await signer.estimateGas (populated))
88132
89133 return populated
134+
135+ method cancelTransaction * (
136+ signer: Signer ,
137+ tx: Transaction
138+ ): Future [TransactionResponse ] {.async , base .} =
139+ # cancels a transaction by sending with a 0-valued transaction to ourselves
140+ # with the failed tx's nonce
141+
142+ without sender =? tx.sender:
143+ raiseSignerError " transaction must have sender"
144+ without nonce =? tx.nonce:
145+ raiseSignerError " transaction must have nonce"
146+
147+ var cancelTx = Transaction (to: sender, value: 0 .u256, nonce: some nonce)
148+ cancelTx = await signer.populateTransaction (cancelTx)
149+ return await signer.sendTransaction (cancelTx)
0 commit comments