Skip to content

Commit 62111cc

Browse files
grypezclaude
andcommitted
fix(evm-wallet): track cumulative spend (totalLimit) in transferNative twin
The transferFungible twin correctly tracked budget via spent/max with pre-reserve + rollback, but transferNative only checked maxAmount (per-call) and silently ignored totalLimit (cumulative cap). Add the same pattern: - cumulativeMax = totalLimit ?? 2^256-1 - Reserve before await, roll back on redeemFn failure Retain the per-call maxAmount body check for test environments where interface guards are mocked out. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3d524bb commit 62111cc

1 file changed

Lines changed: 20 additions & 1 deletion

File tree

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export function makeDelegationTwin(
3838
// method body comparison work correctly regardless of the source.
3939
const maxAmount =
4040
grant.maxAmount === undefined ? undefined : BigInt(grant.maxAmount);
41+
const totalLimit =
42+
grant.totalLimit === undefined ? undefined : BigInt(grant.totalLimit);
4143

4244
const toGuard = to === undefined ? M.string() : M.eq(to);
4345
const amountGuard = maxAmount === undefined ? M.bigint() : M.lte(maxAmount);
@@ -50,21 +52,38 @@ export function makeDelegationTwin(
5052
{ defaultGuards: 'passable' },
5153
);
5254

55+
let spent = 0n;
56+
const cumulativeMax = totalLimit ?? 2n ** 256n - 1n;
57+
5358
const exo = makeDiscoverableExo(
5459
`DelegationTwin:transferNative:${idPrefix}`,
5560
{
5661
async transferNative(recipient: Address, amount: bigint): Promise<Hex> {
5762
if (maxAmount !== undefined && amount > maxAmount) {
5863
throw new Error(`Amount ${amount} exceeds limit ${maxAmount}`);
5964
}
65+
if (amount > cumulativeMax - spent) {
66+
throw new Error(
67+
`Insufficient budget: requested ${amount}, remaining ${cumulativeMax - spent}`,
68+
);
69+
}
70+
71+
// Reserve before the await so concurrent calls see the updated budget.
72+
spent += amount;
6073

6174
const execution: Execution = {
6275
target: recipient,
6376
value: `0x${amount.toString(16)}`,
6477
callData: '0x' as Hex,
6578
};
6679

67-
return redeemFn(execution);
80+
try {
81+
return await redeemFn(execution);
82+
} catch (error) {
83+
// Roll back on redeemFn failure.
84+
spent -= amount;
85+
throw error;
86+
}
6887
},
6988
},
7089
{ transferNative: METHOD_CATALOG.transferNative },

0 commit comments

Comments
 (0)