diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index dcd66d3a2..bbd656f7d 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -74,8 +74,9 @@ export const waitForTransaction = async ( }; export class EthereumChainService extends EthereumChainReader implements IVectorChainService { + private nonces: Map = new Map(); private signers: Map = new Map(); - private queue: PriorityQueue = new PriorityQueue({ concurrency: 1 }); + private queues: Map = new Map(); private evts: { [eventName in ChainServiceEvent]: Evt } = { ...this.disputeEvts, [ChainServiceEvents.TRANSACTION_SUBMITTED]: new Evt(), @@ -95,6 +96,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector parseInt(chainId), typeof signer === "string" ? new Wallet(signer, provider) : (signer.connect(provider) as Signer), ); + this.queues.set(parseInt(chainId), new PriorityQueue({ concurrency: 1 })); }); // TODO: Check to see which tx's are still active / unresolved, and resolve them. @@ -195,21 +197,36 @@ export class EthereumChainService extends EthereumChainReader implements IVector txFn: (gasPrice: BigNumber, nonce: number) => Promise, gasPrice: BigNumber, signer: Signer, + chainId: number, nonce?: number, ): Promise> { // Queue up the execution of the transaction. - return await this.queue.add( - async (): Promise> => { - try { - // Send transaction using the passed in callback. - const actualNonce: number = nonce ?? (await signer.getTransactionCount("pending")); - const response: TransactionResponse | undefined = await txFn(gasPrice, actualNonce); - return Result.ok(response); - } catch (e) { - return Result.fail(e); + if (!this.queues.has(chainId)) { + return Result.fail(new ChainError(ChainError.reasons.SignerNotFound)); + } + // Define task to send tx with proper nonce + const task = async (): Promise> => { + try { + // Send transaction using the passed in callback. + const stored = this.nonces.get(chainId); + const nonceToUse: number = nonce ?? stored ?? (await signer.getTransactionCount("pending")); + const response: TransactionResponse | undefined = await txFn(gasPrice, nonceToUse); + // After calling tx fn, set nonce to the greatest of + // stored, pending, or incremented + const pending = await signer.getTransactionCount("pending"); + const incremented = (response?.nonce ?? nonceToUse) + 1; + // Ensure the nonce you store is *always* the greatest of the values + const toCompare = stored ?? 0; + if (toCompare < pending || toCompare < incremented) { + this.nonces.set(chainId, incremented > pending ? incremented : pending); } - }, - ); + return Result.ok(response); + } catch (e) { + return Result.fail(e); + } + }; + const result = await this.queues.get(chainId)!.add(task); + return result; } /// Check to see if any txs were left in an unfinished state. This should only execute on @@ -389,7 +406,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector { method, methodId, nonce, tryNumber, channelAddress, gasPrice: gasPrice.toString() }, "Attempting to send transaction", ); - const result = await this.sendTx(txFn, gasPrice, signer, nonce); + const result = await this.sendTx(txFn, gasPrice, signer, chainId, nonce); if (!result.isError) { const response = result.getValue(); if (response) {