Priority: High
Description
TxAggregator.execute() never releases the nonce reserved in build() when submission fails before reaching the ledger — either sendTransaction returning status: "ERROR" (sequence never consumed on-chain) or the poll loop timing out. execute() doesn't even receive sourceAccountId/sequence, so there's no way for it to call nonceManager.release(...) on these paths.
Location
engine-bridge/src/tx-aggregator.ts:123-157 (execute)
Current Behavior
async execute(transaction: Transaction, opts: {...} = {}): Promise<...> {
const sendResponse = await this.rpc.call(async (server) => server.sendTransaction(transaction));
if (sendResponse.status === "ERROR") {
throw new Error(`TxAggregator: sendTransaction failed with status ERROR: ...`);
}
while (true) {
if (Date.now() - startTime > timeoutMs) {
throw new Error(`TxAggregator: transaction execution timed out after ${timeoutMs}ms`);
}
...
Expected Behavior
On a submission failure known not to have consumed the on-chain sequence, the reserved sequence should be released back to NonceManager.
Repro / Evidence
const nm = new NonceManager(rpc);
const aggregator = new TxAggregator(rpc, nm, new GasOracle(), Networks.TESTNET);
const batch = await aggregator.build({ ... });
await expect(aggregator.execute(batch.transaction, {...})).rejects.toThrow();
const nextSeq = await nm.reserve(signer.publicKey());
expect(nextSeq).toBe(batch.sequence); // FAILS -- nextSeq is batch.sequence + 1
Impact
Every failed/rejected/timed-out submission permanently burns a sequence number in the in-process cache without touching the chain. Under elevated error rates, the cache drifts ahead of the real account sequence until all transactions from that wallet fail with tx_bad_seq — a full outage of the fund-moving path requiring manual refresh().
Suggested Fix
Thread sourceAccountId/sequence into execute() and call nonceManager.release(...) in the ERROR and timeout catch paths.
Acceptance Criteria
Definition of Done
Priority: High
Description
TxAggregator.execute()never releases the nonce reserved inbuild()when submission fails before reaching the ledger — eithersendTransactionreturningstatus: "ERROR"(sequence never consumed on-chain) or the poll loop timing out.execute()doesn't even receivesourceAccountId/sequence, so there's no way for it to callnonceManager.release(...)on these paths.Location
engine-bridge/src/tx-aggregator.ts:123-157(execute)Current Behavior
Expected Behavior
On a submission failure known not to have consumed the on-chain sequence, the reserved sequence should be released back to
NonceManager.Repro / Evidence
Impact
Every failed/rejected/timed-out submission permanently burns a sequence number in the in-process cache without touching the chain. Under elevated error rates, the cache drifts ahead of the real account sequence until all transactions from that wallet fail with
tx_bad_seq— a full outage of the fund-moving path requiring manualrefresh().Suggested Fix
Thread
sourceAccountId/sequenceintoexecute()and callnonceManager.release(...)in the ERROR and timeout catch paths.Acceptance Criteria
execute()releases the sequence onsendTransactionERROR.execute()releases the sequence on poll-loop timeout.Definition of Done