Skip to content

Commit 4ffabfb

Browse files
authored
fix: populate gasLimit in eip7702 batch execution (#77)
1 parent 5994736 commit 4ffabfb

File tree

5 files changed

+150
-33
lines changed

5 files changed

+150
-33
lines changed

packages/sdk/src/client/common/contract/caller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export async function executeDeployBatch(
338338
executions: data.executions,
339339
pendingMessage,
340340
gas,
341+
authorizationList: data.authorizationList,
341342
},
342343
logger,
343344
);
@@ -838,6 +839,7 @@ export async function executeUpgradeBatch(
838839
executions: data.executions,
839840
pendingMessage,
840841
gas,
842+
authorizationList: data.authorizationList,
841843
},
842844
logger,
843845
);

packages/sdk/src/client/common/contract/eip7702.ts

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface EstimateBatchGasOptions {
6363
publicClient: PublicClient;
6464
account: Address;
6565
executions: Execution[];
66+
authorizationList?: SignAuthorizationReturnType[];
6667
}
6768

6869
/**
@@ -71,7 +72,7 @@ export interface EstimateBatchGasOptions {
7172
* Use this to get cost estimate before prompting user for confirmation.
7273
*/
7374
export async function estimateBatchGas(options: EstimateBatchGasOptions): Promise<GasEstimate> {
74-
const { publicClient, account, executions } = options;
75+
const { publicClient, account, executions, authorizationList } = options;
7576

7677
const executeBatchData = encodeExecuteBatchData(executions);
7778

@@ -83,6 +84,7 @@ export async function estimateBatchGas(options: EstimateBatchGasOptions): Promis
8384
account,
8485
to: account,
8586
data: executeBatchData,
87+
authorizationList,
8688
}),
8789
]);
8890

@@ -113,6 +115,8 @@ export interface ExecuteBatchOptions {
113115
pendingMessage: string;
114116
/** Optional gas params from estimation */
115117
gas?: GasEstimate;
118+
/** Optional pre-created authorization list (skips internal creation if provided) */
119+
authorizationList?: SignAuthorizationReturnType[];
116120
}
117121

118122
/**
@@ -133,12 +137,74 @@ export async function checkERC7702Delegation(
133137
return code.toLowerCase() === expectedCode.toLowerCase();
134138
}
135139

140+
/**
141+
* Options for creating an authorization list
142+
*/
143+
export interface CreateAuthorizationListOptions {
144+
walletClient: WalletClient;
145+
publicClient: PublicClient;
146+
environmentConfig: EnvironmentConfig;
147+
}
148+
149+
/**
150+
* Create an authorization list for EIP-7702 delegation if needed
151+
*
152+
* Returns undefined if the account is already delegated.
153+
* Use this to create the auth list before gas estimation to get accurate estimates.
154+
*/
155+
export async function createAuthorizationList(
156+
options: CreateAuthorizationListOptions,
157+
): Promise<SignAuthorizationReturnType[] | undefined> {
158+
const { walletClient, publicClient, environmentConfig } = options;
159+
160+
const account = walletClient.account;
161+
if (!account) {
162+
throw new Error("Wallet client must have an account");
163+
}
164+
165+
// Check if already delegated
166+
const isDelegated = await checkERC7702Delegation(
167+
publicClient,
168+
account.address,
169+
environmentConfig.erc7702DelegatorAddress as Address,
170+
);
171+
172+
if (isDelegated) {
173+
return undefined;
174+
}
175+
176+
// Create authorization
177+
const transactionNonce = await publicClient.getTransactionCount({
178+
address: account.address,
179+
blockTag: "pending",
180+
});
181+
182+
const chainId = await publicClient.getChainId();
183+
const authorizationNonce = transactionNonce + 1;
184+
185+
const signedAuthorization = await walletClient.signAuthorization({
186+
account,
187+
contractAddress: environmentConfig.erc7702DelegatorAddress as Address,
188+
chainId: chainId,
189+
nonce: Number(authorizationNonce),
190+
});
191+
192+
return [signedAuthorization];
193+
}
194+
136195
/**
137196
* Execute batch of operations via EIP-7702 delegator
138197
*/
139198
export async function executeBatch(options: ExecuteBatchOptions, logger: Logger = noopLogger): Promise<Hex> {
140-
const { walletClient, publicClient, environmentConfig, executions, pendingMessage, gas } =
141-
options;
199+
const {
200+
walletClient,
201+
publicClient,
202+
environmentConfig,
203+
executions,
204+
pendingMessage,
205+
gas,
206+
authorizationList: providedAuthList,
207+
} = options;
142208

143209
const account = walletClient.account;
144210
if (!account) {
@@ -152,35 +218,37 @@ export async function executeBatch(options: ExecuteBatchOptions, logger: Logger
152218

153219
const executeBatchData = encodeExecuteBatchData(executions);
154220

155-
// Check if account is delegated
156-
const isDelegated = await checkERC7702Delegation(
157-
publicClient,
158-
account.address,
159-
environmentConfig.erc7702DelegatorAddress as Address,
160-
);
161-
162-
// 4. Create authorization if needed
163-
let authorizationList: Array<SignAuthorizationReturnType> = [];
164-
165-
if (!isDelegated) {
166-
const transactionNonce = await publicClient.getTransactionCount({
167-
address: account.address,
168-
blockTag: "pending",
169-
});
221+
// Use provided authorization list or create one if needed
222+
let authorizationList: Array<SignAuthorizationReturnType> = providedAuthList || [];
223+
if (authorizationList.length === 0) {
224+
// Check if account is delegated
225+
const isDelegated = await checkERC7702Delegation(
226+
publicClient,
227+
account.address,
228+
environmentConfig.erc7702DelegatorAddress as Address,
229+
);
230+
231+
// Create authorization if needed
232+
if (!isDelegated) {
233+
const transactionNonce = await publicClient.getTransactionCount({
234+
address: account.address,
235+
blockTag: "pending",
236+
});
170237

171-
const chainId = await publicClient.getChainId();
172-
const authorizationNonce = transactionNonce + 1;
238+
const chainId = await publicClient.getChainId();
239+
const authorizationNonce = transactionNonce + 1;
173240

174-
logger.debug("Using wallet client signing for EIP-7702 authorization");
241+
logger.debug("Using wallet client signing for EIP-7702 authorization");
175242

176-
const signedAuthorization = await walletClient.signAuthorization({
177-
account: account.address,
178-
contractAddress: environmentConfig.erc7702DelegatorAddress as Address,
179-
chainId: chainId,
180-
nonce: Number(authorizationNonce),
181-
});
243+
const signedAuthorization = await walletClient.signAuthorization({
244+
account,
245+
contractAddress: environmentConfig.erc7702DelegatorAddress as Address,
246+
chainId: chainId,
247+
nonce: Number(authorizationNonce),
248+
});
182249

183-
authorizationList = [signedAuthorization];
250+
authorizationList = [signedAuthorization];
251+
}
184252
}
185253

186254
// 5. Show pending message
@@ -201,6 +269,9 @@ export async function executeBatch(options: ExecuteBatchOptions, logger: Logger
201269
}
202270

203271
// Add gas params if provided
272+
if (gas?.gasLimit) {
273+
txRequest.gas = gas.gasLimit;
274+
}
204275
if (gas?.maxFeePerGas) {
205276
txRequest.maxFeePerGas = gas.maxFeePerGas;
206277
}

packages/sdk/src/client/common/types/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Core types for ECloud SDK
33
*/
44

5-
import { Address, Hex } from "viem";
5+
import { Address, Hex, SignAuthorizationReturnType } from "viem";
66
import { GasEstimate } from "../contract/caller";
77

88
export type AppId = Address;
@@ -137,6 +137,8 @@ export interface PreparedDeployData {
137137
salt: Uint8Array;
138138
/** Batch executions to be sent */
139139
executions: Array<{ target: Address; value: bigint; callData: Hex }>;
140+
/** Pre-created authorization list for gas estimation accuracy (optional) */
141+
authorizationList?: SignAuthorizationReturnType[];
140142
}
141143

142144
/** Data-only batch for upgrade (clients provided by module) */
@@ -145,6 +147,8 @@ export interface PreparedUpgradeData {
145147
appId: AppId;
146148
/** Batch executions to be sent */
147149
executions: Array<{ target: Address; value: bigint; callData: Hex }>;
150+
/** Pre-created authorization list for gas estimation accuracy (optional) */
151+
authorizationList?: SignAuthorizationReturnType[];
148152
}
149153

150154
/** Prepared deployment ready for execution */

packages/sdk/src/client/modules/compute/app/deploy.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
prepareDeployBatch,
2828
executeDeployBatch,
2929
} from "../../../common/contract/caller";
30-
import { estimateBatchGas } from "../../../common/contract/eip7702";
30+
import { estimateBatchGas, createAuthorizationList } from "../../../common/contract/eip7702";
3131
import { type GasEstimate } from "../../../common/contract/caller";
3232
import { watchUntilRunning } from "../../../common/contract/watcher";
3333
import {
@@ -237,19 +237,29 @@ export async function prepareDeployFromVerifiableBuild(
237237
logger,
238238
);
239239

240+
// Create authorization list if not delegated (for accurate gas estimation)
241+
logger.debug("Checking delegation status...");
242+
const authorizationList = await createAuthorizationList({
243+
walletClient: batch.walletClient,
244+
publicClient: batch.publicClient,
245+
environmentConfig: batch.environmentConfig,
246+
});
247+
240248
// Estimate gas
241249
logger.debug("Estimating gas...");
242250
const gasEstimate = await estimateBatchGas({
243251
publicClient: batch.publicClient,
244252
account: batch.walletClient.account!.address,
245253
executions: batch.executions,
254+
authorizationList,
246255
});
247256

248257
// Extract only data fields for public type (clients stay internal)
249258
const data: PreparedDeployData = {
250259
appId: batch.appId,
251260
salt: batch.salt,
252261
executions: batch.executions,
262+
authorizationList,
253263
};
254264

255265
return {
@@ -596,19 +606,29 @@ export async function prepareDeploy(
596606
logger,
597607
);
598608

599-
// 9. Estimate gas for the batch
609+
// 9. Create authorization list if not delegated (for accurate gas estimation)
610+
logger.debug("Checking delegation status...");
611+
const authorizationList = await createAuthorizationList({
612+
walletClient: batch.walletClient,
613+
publicClient: batch.publicClient,
614+
environmentConfig: batch.environmentConfig,
615+
});
616+
617+
// 10. Estimate gas for the batch
600618
logger.debug("Estimating gas...");
601619
const gasEstimate = await estimateBatchGas({
602620
publicClient: batch.publicClient,
603621
account: batch.walletClient.account!.address,
604622
executions: batch.executions,
623+
authorizationList,
605624
});
606625

607626
// Extract only data fields for public type (clients stay internal)
608627
const data: PreparedDeployData = {
609628
appId: batch.appId,
610629
salt: batch.salt,
611630
executions: batch.executions,
631+
authorizationList,
612632
};
613633

614634
return {

packages/sdk/src/client/modules/compute/app/upgrade.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
executeUpgradeBatch,
2727
type GasEstimate,
2828
} from "../../../common/contract/caller";
29-
import { estimateBatchGas } from "../../../common/contract/eip7702";
29+
import { estimateBatchGas, createAuthorizationList } from "../../../common/contract/eip7702";
3030
import { watchUntilUpgradeComplete } from "../../../common/contract/watcher";
3131
import {
3232
validateAppID,
@@ -185,17 +185,27 @@ export async function prepareUpgradeFromVerifiableBuild(
185185
imageRef: options.imageRef,
186186
});
187187

188+
// Create authorization list if not delegated (for accurate gas estimation)
189+
logger.debug("Checking delegation status...");
190+
const authorizationList = await createAuthorizationList({
191+
walletClient: batch.walletClient,
192+
publicClient: batch.publicClient,
193+
environmentConfig: batch.environmentConfig,
194+
});
195+
188196
logger.debug("Estimating gas...");
189197
const gasEstimate = await estimateBatchGas({
190198
publicClient: batch.publicClient,
191199
account: batch.walletClient.account!.address,
192200
executions: batch.executions,
201+
authorizationList,
193202
});
194203

195204
// Extract only data fields for public type (clients stay internal)
196205
const data: PreparedUpgradeData = {
197206
appId: batch.appId,
198207
executions: batch.executions,
208+
authorizationList,
199209
};
200210

201211
return {
@@ -462,18 +472,28 @@ export async function prepareUpgrade(
462472
imageRef: finalImageRef,
463473
});
464474

465-
// 7. Estimate gas for the batch
475+
// 7. Create authorization list if not delegated (for accurate gas estimation)
476+
logger.debug("Checking delegation status...");
477+
const authorizationList = await createAuthorizationList({
478+
walletClient: batch.walletClient,
479+
publicClient: batch.publicClient,
480+
environmentConfig: batch.environmentConfig,
481+
});
482+
483+
// 8. Estimate gas for the batch
466484
logger.debug("Estimating gas...");
467485
const gasEstimate = await estimateBatchGas({
468486
publicClient: batch.publicClient,
469487
account: batch.walletClient.account!.address,
470488
executions: batch.executions,
489+
authorizationList,
471490
});
472491

473492
// Extract only data fields for public type (clients stay internal)
474493
const data: PreparedUpgradeData = {
475494
appId: batch.appId,
476495
executions: batch.executions,
496+
authorizationList,
477497
};
478498

479499
return {

0 commit comments

Comments
 (0)