Skip to content

Commit 6961e09

Browse files
[SDK] simulate inner transactions before bundling user ops (#2714)
1 parent 49354c6 commit 6961e09

File tree

4 files changed

+48
-18
lines changed

4 files changed

+48
-18
lines changed

.changeset/eight-gorillas-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@thirdweb-dev/wallets": patch
3+
---
4+
5+
Simulate inner transaction before bundling user ops

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

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { ethers, BigNumber, BigNumberish, providers } from "ethers";
1+
import { ethers, BigNumber, type BigNumberish, type providers } from "ethers";
22
import {
3-
EntryPoint,
3+
type EntryPoint,
44
EntryPoint__factory,
5-
UserOperationStruct,
5+
type UserOperationStruct,
66
} from "@account-abstraction/contracts";
77

8-
import { TransactionDetailsForUserOp } from "./transaction-details";
8+
import type { TransactionDetailsForUserOp } from "./transaction-details";
99
import { getUserOpHashV06 } from "./utils";
1010
import {
1111
CeloAlfajoresTestnet,
1212
CeloBaklavaTestnet,
1313
Celo,
1414
} from "@thirdweb-dev/chains";
15-
import { Transaction, getDynamicFeeData } from "@thirdweb-dev/sdk";
16-
import { HttpRpcClient } from "./http-rpc-client";
15+
import { type Transaction, getDynamicFeeData } from "@thirdweb-dev/sdk";
16+
import type { HttpRpcClient } from "./http-rpc-client";
1717
import type { BaseApiParams, PaymasterAPI, UserOpOptions } from "../types";
1818
import { isTwUrl } from "../../../utils/url";
1919

@@ -183,14 +183,6 @@ export abstract class BaseAccountAPI {
183183
info: TransactionDetailsForUserOp,
184184
options?: UserOpOptions,
185185
): Promise<UserOperationStruct> {
186-
// construct the userOp without gasLimit or preVerifictaionGas
187-
const initCode = await this.getInitCode();
188-
const value = parseNumber(info.value) ?? BigNumber.from(0);
189-
const callData = options?.batchData
190-
? info.data
191-
: await this.prepareExecute(info.target, value, info.data).then((tx) =>
192-
tx.encode(),
193-
);
194186
let { maxFeePerGas, maxPriorityFeePerGas } = info;
195187
// get fees from bundler if available
196188
if (isTwUrl(httpRpcClient.bundlerUrl)) {
@@ -232,6 +224,25 @@ export abstract class BaseAccountAPI {
232224
this.getAccountAddress(),
233225
info.nonce ? Promise.resolve(info.nonce) : this.getNonce(),
234226
]);
227+
const initCode = await this.getInitCode();
228+
const value = parseNumber(info.value) ?? BigNumber.from(0);
229+
const callData = options?.batchData
230+
? info.data
231+
: await this.prepareExecute(info.target, value, info.data).then(
232+
async (tx) => {
233+
if (!info.gasLimit) {
234+
// estimate gas on the inner transactions to simulate
235+
// bundler would not revert otherwise
236+
await this.provider.estimateGas({
237+
from: sender,
238+
to: info.target,
239+
data: info.data,
240+
value: value,
241+
});
242+
}
243+
return tx.encode();
244+
},
245+
);
235246
const partialOp: UserOperationStruct = {
236247
sender,
237248
nonce,

packages/thirdweb/src/wallets/smart/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,6 @@ async function createSmartAccount(
217217
async signTypedData(typedData: any) {
218218
return options.personalAccount.signTypedData(typedData);
219219
},
220-
async estimateGas(tx: PreparedTransaction): Promise<bigint> {
221-
void tx; // linter
222-
return 0n;
223-
},
224220
};
225221
return account;
226222
}

packages/thirdweb/src/wallets/smart/smart-wallet-integration.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type ThirdwebContract, getContract } from "../../contract/contract.js";
66
import { balanceOf } from "../../extensions/erc1155/__generated__/IERC1155/read/balanceOf.js";
77
import { claimTo } from "../../extensions/erc1155/drops/write/claimTo.js";
88
import { checkContractWalletSignature } from "../../extensions/erc1271/checkContractWalletSignature.js";
9+
import { setContractURI } from "../../extensions/marketplace/__generated__/IMarketplace/write/setContractURI.js";
910
import { estimateGasCost } from "../../transaction/actions/estimate-gas-cost.js";
1011
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
1112
import { sendBatchTransaction } from "../../transaction/actions/send-batch-transaction.js";
@@ -60,6 +61,23 @@ describe.runIf(process.env.TW_SECRET_KEY)(
6061
expect(smartWalletAddress).toHaveLength(42);
6162
});
6263

64+
it("should revert on unsuccessful transactions", async () => {
65+
const tx = sendAndConfirmTransaction({
66+
transaction: setContractURI({
67+
contract,
68+
uri: "https://example.com",
69+
}),
70+
account: smartAccount,
71+
});
72+
73+
await expect(tx).rejects.toMatchInlineSnapshot(`
74+
[TransactionError: Error - Not authorized
75+
76+
contract: 0x6A7a26c9a595E6893C255C9dF0b593e77518e0c3
77+
chainId: 421614]
78+
`);
79+
});
80+
6381
it("can execute a tx", async () => {
6482
const tx = await sendAndConfirmTransaction({
6583
transaction: claimTo({

0 commit comments

Comments
 (0)