Skip to content

Commit 556ad8e

Browse files
fix batch execute estimation
1 parent bb58424 commit 556ad8e

File tree

4 files changed

+98
-68
lines changed

4 files changed

+98
-68
lines changed

packages/wallets/src/evm/connectors/smart-wallet/index.ts

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
import { AccountAPI } from "./lib/account";
2828
import { AddressZero } from "@account-abstraction/utils";
2929
import { TransactionDetailsForUserOp } from "./lib/transaction-details";
30+
import { BatchData } from "./lib/base-api";
3031

3132
export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
3233
protected config: SmartWalletConfig;
@@ -203,14 +204,14 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
203204
throw new Error("Personal wallet not connected");
204205
}
205206
const signer = await this.getSigner();
206-
const batchData = await this.prepareBatchTx(transactions);
207+
const { tx, batchData } = await this.prepareBatchTx(transactions);
207208
return await signer.sendTransaction(
208209
{
209210
to: await signer.getAddress(),
210-
data: batchData.encode(),
211+
data: tx.encode(),
211212
value: 0,
212213
},
213-
true, // batched tx flag
214+
batchData,
214215
);
215216
}
216217

@@ -258,14 +259,14 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
258259
throw new Error("Personal wallet not connected");
259260
}
260261
const signer = await this.getSigner();
261-
const batchData = await this.prepareBatchRaw(transactions);
262+
const batch = await this.prepareBatchRaw(transactions);
262263
return signer.sendTransaction(
263264
{
264265
to: await signer.getAddress(),
265-
data: batchData.encode(),
266+
data: batch.tx.encode(),
266267
value: 0,
267268
},
268-
true, // batched tx flag
269+
batch.batchData, // batched tx flag
269270
);
270271
}
271272

@@ -285,15 +286,11 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
285286
if (!this.accountApi) {
286287
throw new Error("Personal wallet not connected");
287288
}
288-
console.log("single", transaction.getTarget(), transaction.encode().length);
289-
return this.estimateTx(
290-
{
291-
target: transaction.getTarget(),
292-
data: transaction.encode(),
293-
value: await transaction.getValue(),
294-
},
295-
false,
296-
);
289+
return this.estimateTx({
290+
target: transaction.getTarget(),
291+
data: transaction.encode(),
292+
value: await transaction.getValue(),
293+
});
297294
}
298295

299296
async estimateRaw(
@@ -303,29 +300,25 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
303300
throw new Error("Personal wallet not connected");
304301
}
305302
const tx = await ethers.utils.resolveProperties(transaction);
306-
return this.estimateTx(
307-
{
308-
target: tx.to || AddressZero,
309-
data: tx.data?.toString() || "",
310-
value: tx.value || BigNumber.from(0),
311-
},
312-
false,
313-
);
303+
return this.estimateTx({
304+
target: tx.to || AddressZero,
305+
data: tx.data?.toString() || "",
306+
value: tx.value || BigNumber.from(0),
307+
});
314308
}
315309

316310
async estimateBatch(transactions: Transaction<any>[]) {
317311
if (!this.accountApi) {
318312
throw new Error("Personal wallet not connected");
319313
}
320-
const batch = await this.prepareBatchTx(transactions);
321-
console.log("batch", batch.getTarget(), batch.encode().length);
314+
const { tx, batchData } = await this.prepareBatchTx(transactions);
322315
return this.estimateTx(
323316
{
324-
target: batch.getTarget(),
325-
data: batch.encode(),
326-
value: await batch.getValue(),
317+
target: tx.getTarget(),
318+
data: tx.encode(),
319+
value: await tx.getValue(),
327320
},
328-
true,
321+
batchData,
329322
);
330323
}
331324

@@ -335,14 +328,14 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
335328
if (!this.accountApi) {
336329
throw new Error("Personal wallet not connected");
337330
}
338-
const batch = await this.prepareBatchRaw(transactions);
331+
const { tx, batchData } = await this.prepareBatchRaw(transactions);
339332
return this.estimateTx(
340333
{
341-
target: batch.getTarget(),
342-
data: batch.encode(),
343-
value: await batch.getValue(),
334+
target: tx.getTarget(),
335+
data: tx.encode(),
336+
value: await tx.getValue(),
344337
},
345-
true,
338+
batchData,
346339
);
347340
}
348341

@@ -357,16 +350,16 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
357350
if (!this.accountApi) {
358351
throw new Error("Personal wallet not connected");
359352
}
360-
if (await this.accountApi.isAcountDeployed()) {
361-
throw new Error("Smart wallet already deployed");
362-
}
363353
const signer = await this.getSigner();
364354
const tx = await signer.sendTransaction(
365355
{
366356
to: await signer.getAddress(),
367357
data: "0x",
368358
},
369-
true, // batched tx flag to avoid hitting the Router fallback method
359+
{
360+
targets: [],
361+
data: [],
362+
}, // batched tx flag to avoid hitting the Router fallback method
370363
);
371364
const receipt = await tx.wait();
372365
return { receipt };
@@ -533,7 +526,10 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
533526

534527
/// PRIVATE METHODS
535528

536-
private async estimateTx(tx: TransactionDetailsForUserOp, batched: boolean) {
529+
private async estimateTx(
530+
tx: TransactionDetailsForUserOp,
531+
batchData?: BatchData,
532+
) {
537533
if (!this.accountApi) {
538534
throw new Error("Personal wallet not connected");
539535
}
@@ -547,7 +543,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
547543
}
548544
const [{ callGasLimit: transactionGasLimit }, gasPrice] = await Promise.all(
549545
[
550-
await this.accountApi.encodeUserOpCallDataAndGasLimit(tx, batched),
546+
this.accountApi.encodeUserOpCallDataAndGasLimit(tx, batchData),
551547
getGasPrice(provider),
552548
],
553549
);
@@ -595,7 +591,13 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
595591
const targets = resolvedTxs.map((tx) => tx.to || AddressZero);
596592
const data = resolvedTxs.map((tx) => tx.data || "0x");
597593
const values = resolvedTxs.map((tx) => tx.value || BigNumber.from(0));
598-
return this.accountApi.prepareExecuteBatch(targets, values, data);
594+
return {
595+
tx: await this.accountApi.prepareExecuteBatch(targets, values, data),
596+
batchData: {
597+
targets,
598+
data,
599+
},
600+
};
599601
}
600602

601603
private async prepareBatchTx(transactions: Transaction<any>[]) {
@@ -605,6 +607,12 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
605607
const targets = transactions.map((tx) => tx.getTarget());
606608
const data = transactions.map((tx) => tx.encode());
607609
const values = await Promise.all(transactions.map((tx) => tx.getValue()));
608-
return this.accountApi.prepareExecuteBatch(targets, values, data);
610+
return {
611+
tx: await this.accountApi.prepareExecuteBatch(targets, values, data),
612+
batchData: {
613+
targets,
614+
data,
615+
},
616+
};
609617
}
610618
}

packages/wallets/src/evm/connectors/smart-wallet/lib/base-api.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { ethers, BigNumber, BigNumberish, providers, utils } from "ethers";
1+
import {
2+
ethers,
3+
BigNumber,
4+
BigNumberish,
5+
providers,
6+
utils,
7+
BytesLike,
8+
} from "ethers";
29
import {
310
EntryPoint,
411
EntryPoint__factory,
@@ -21,6 +28,11 @@ import {
2128
} from "@thirdweb-dev/chains";
2229
import { Transaction, getDynamicFeeData } from "@thirdweb-dev/sdk";
2330

31+
export type BatchData = {
32+
targets: (string | undefined)[];
33+
data: BytesLike[];
34+
};
35+
2436
export interface BaseApiParams {
2537
provider: providers.Provider;
2638
entryPointAddress: string;
@@ -186,27 +198,40 @@ export abstract class BaseAccountAPI {
186198

187199
async encodeUserOpCallDataAndGasLimit(
188200
detailsForUserOp: TransactionDetailsForUserOp,
189-
batched: boolean,
201+
batchData?: BatchData,
190202
): Promise<{ callData: string; callGasLimit: BigNumber }> {
191203
const value = parseNumber(detailsForUserOp.value) ?? BigNumber.from(0);
192-
const callData = batched
204+
const callData = batchData
193205
? detailsForUserOp.data
194206
: await this.prepareExecute(
195207
detailsForUserOp.target,
196208
value,
197209
detailsForUserOp.data,
198210
).then((tx) => tx.encode());
199211

200-
let callGasLimit;
212+
let callGasLimit: BigNumber;
201213
const isPhantom = await this.checkAccountPhantom();
202214
if (isPhantom) {
203215
// when the account is not deployed yet, we simulate the call to the target contract directly
204-
callGasLimit = await this.provider.estimateGas({
205-
from: this.getAccountAddress(),
206-
to: detailsForUserOp.target,
207-
data: detailsForUserOp.data,
208-
});
209-
console.log("estimated gas limit", callGasLimit.toString());
216+
if (batchData) {
217+
const limits = await Promise.all(
218+
batchData.targets.map((_, i) =>
219+
this.provider.estimateGas({
220+
from: this.getAccountAddress(),
221+
to: batchData.targets[i],
222+
data: batchData.data[i],
223+
}),
224+
),
225+
);
226+
callGasLimit = limits.reduce((a, b) => a.add(b), BigNumber.from(0));
227+
} else {
228+
callGasLimit = await this.provider.estimateGas({
229+
from: this.getAccountAddress(),
230+
to: detailsForUserOp.target,
231+
data: detailsForUserOp.data,
232+
});
233+
}
234+
210235
// add 20% overhead for entrypoint checks
211236
callGasLimit = callGasLimit.mul(120).div(100);
212237
// if the estimation is too low, we use a fixed value of 500k
@@ -274,10 +299,10 @@ export abstract class BaseAccountAPI {
274299
*/
275300
async createUnsignedUserOp(
276301
info: TransactionDetailsForUserOp,
277-
batched: boolean,
302+
batchData?: BatchData,
278303
): Promise<UserOperationStruct> {
279304
const { callData, callGasLimit } =
280-
await this.encodeUserOpCallDataAndGasLimit(info, batched);
305+
await this.encodeUserOpCallDataAndGasLimit(info, batchData);
281306
const initCode = await this.getInitCode();
282307

283308
const initGas = await this.estimateCreationGas(initCode);
@@ -391,10 +416,10 @@ export abstract class BaseAccountAPI {
391416
*/
392417
async createSignedUserOp(
393418
info: TransactionDetailsForUserOp,
394-
batched: boolean,
419+
batchData?: BatchData,
395420
): Promise<UserOperationStruct> {
396421
return await this.signUserOp(
397-
await this.createUnsignedUserOp(info, batched),
422+
await this.createUnsignedUserOp(info, batchData),
398423
);
399424
}
400425

packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-provider.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,12 @@ export class ERC4337EthersProvider extends providers.BaseProvider {
6262
if (method === "estimateGas") {
6363
// hijack this to estimate gas from the entrypoint instead
6464
const { callGasLimit } =
65-
await this.smartAccountAPI.encodeUserOpCallDataAndGasLimit(
66-
{
67-
target: params.transaction.to,
68-
data: params.transaction.data,
69-
value: params.transaction.value,
70-
gasLimit: params.transaction.gasLimit,
71-
},
72-
false, // TODO check this
73-
);
65+
await this.smartAccountAPI.encodeUserOpCallDataAndGasLimit({
66+
target: params.transaction.to,
67+
data: params.transaction.data,
68+
value: params.transaction.value,
69+
gasLimit: params.transaction.gasLimit,
70+
});
7471
return callGasLimit;
7572
}
7673
return await this.originalProvider.perform(method, params);

packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ethers, providers, utils } from "ethers";
22

33
import { Bytes, Signer } from "ethers";
44
import { ClientConfig } from "@account-abstraction/sdk";
5-
import { BaseAccountAPI } from "./base-api";
5+
import { BaseAccountAPI, BatchData } from "./base-api";
66
import type { ERC4337EthersProvider } from "./erc4337-provider";
77
import { HttpRpcClient } from "./http-rpc-client";
88
import { randomNonce } from "./utils";
@@ -36,7 +36,7 @@ export class ERC4337EthersSigner extends Signer {
3636
// This one is called by Contract. It signs the request and passes in to Provider to be sent.
3737
async sendTransaction(
3838
transaction: utils.Deferrable<providers.TransactionRequest>,
39-
batched: boolean = false,
39+
batchData?: BatchData,
4040
): Promise<providers.TransactionResponse> {
4141
const tx = await ethers.utils.resolveProperties(transaction);
4242
await this.verifyAllNecessaryFields(tx);
@@ -50,7 +50,7 @@ export class ERC4337EthersSigner extends Signer {
5050
gasLimit: tx.gasLimit,
5151
nonce: multidimensionalNonce,
5252
},
53-
batched,
53+
batchData,
5454
);
5555

5656
const transactionResponse =

0 commit comments

Comments
 (0)