Skip to content

Commit 4712d24

Browse files
grypezclaude
andcommitted
fix(evm-wallet-experiment): reserve spend budget before await, rollback on failure
Committing after redeemFn resolved left a window where two concurrent calls could both pass the remaining() check and together exceed the budget. Commit before the await to atomically reserve the budget, then rollback if redeemFn throws. This is the pattern rollback() was designed for but was never called. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6d32cb0 commit 4712d24

1 file changed

Lines changed: 10 additions & 4 deletions

File tree

packages/evm-wallet-experiment/src/lib/delegation-twin.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,24 @@ export function makeDelegationTwin(
177177
`Insufficient budget: requested ${trackAmount}, remaining ${tracker.remaining()}`,
178178
);
179179
}
180+
// Reserve before the await so concurrent calls see the updated budget
181+
// and cannot both pass the check. Roll back if redeemFn fails.
182+
tracker.commit(trackAmount);
180183
}
181184

182185
const execution = entry.buildExecution(
183186
token ?? ('' as Address),
184187
normalizedArgs,
185188
);
186189

187-
const txHash = await redeemFn(execution);
188-
if (tracker && trackAmount !== undefined) {
189-
tracker.commit(trackAmount);
190+
try {
191+
return await redeemFn(execution);
192+
} catch (error) {
193+
if (tracker && trackAmount !== undefined) {
194+
tracker.rollback(trackAmount);
195+
}
196+
throw error;
190197
}
191-
return txHash;
192198
};
193199

194200
const methods: Record<string, (...args: unknown[]) => unknown> = {

0 commit comments

Comments
 (0)