Skip to content

Commit f3234c3

Browse files
authored
[thirdweb/server-wallet] fix address used for simulation for ERC4337 (#7581)
1 parent 91e3781 commit f3234c3

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

.changeset/dirty-candies-shop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
fix engine server wallet usage with session keys

packages/thirdweb/src/engine/server-wallet.test.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { verifyTypedData } from "src/auth/verify-typed-data.js";
2+
import { toWei } from "src/utils/units.js";
23
import { beforeAll, describe, expect, it } from "vitest";
34
import { TEST_CLIENT } from "../../test/src/test-clients.js";
45
import { TEST_ACCOUNT_B } from "../../test/src/test-wallets.js";
@@ -8,6 +9,9 @@ import { baseSepolia } from "../chains/chain-definitions/base-sepolia.js";
89
import { sepolia } from "../chains/chain-definitions/sepolia.js";
910
import { getContract } from "../contract/contract.js";
1011
import { setContractURI } from "../extensions/common/__generated__/IContractMetadata/write/setContractURI.js";
12+
import { claimTo as claimToERC20 } from "../extensions/erc20/drops/write/claimTo.js";
13+
import { getBalance } from "../extensions/erc20/read/getBalance.js";
14+
import { transfer } from "../extensions/erc20/write/transfer.js";
1115
import { setApprovalForAll } from "../extensions/erc1155/__generated__/IERC1155/write/setApprovalForAll.js";
1216
import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js";
1317
import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js";
@@ -217,7 +221,7 @@ describe.runIf(
217221
).rejects.toThrow();
218222
});
219223

220-
it("should send a session key tx", async () => {
224+
it("should send a basic session key tx", async () => {
221225
const sessionKeyAccountAddress = process.env
222226
.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string;
223227
const personalAccount = await generateAccount({
@@ -272,5 +276,117 @@ describe.runIf(
272276
});
273277
expect(tx).toBeDefined();
274278
});
279+
280+
it("should send a session key tx with ERC20 claiming and transfer", async () => {
281+
// The EOA is the session key signer, ie, it has session key permissions on the generated smart account
282+
const sessionKeyAccountAddress = process.env
283+
.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string;
284+
const personalAccount = await generateAccount({
285+
client: TEST_CLIENT,
286+
});
287+
const smart = smartWallet({
288+
chain: arbitrumSepolia,
289+
sessionKey: {
290+
address: sessionKeyAccountAddress,
291+
permissions: {
292+
approvedTargets: "*",
293+
},
294+
},
295+
sponsorGas: true,
296+
});
297+
const smartAccount = await smart.connect({
298+
client: TEST_CLIENT,
299+
personalAccount,
300+
});
301+
expect(smartAccount.address).toBeDefined();
302+
303+
const signers = await getAllActiveSigners({
304+
contract: getContract({
305+
address: smartAccount.address,
306+
chain: arbitrumSepolia,
307+
client: TEST_CLIENT,
308+
}),
309+
});
310+
expect(signers.map((s) => s.signer)).toContain(sessionKeyAccountAddress);
311+
312+
const serverWallet = Engine.serverWallet({
313+
address: sessionKeyAccountAddress,
314+
chain: arbitrumSepolia,
315+
client: TEST_CLIENT,
316+
executionOptions: {
317+
entrypointVersion: "0.6",
318+
signerAddress: sessionKeyAccountAddress,
319+
smartAccountAddress: smartAccount.address,
320+
type: "ERC4337",
321+
},
322+
vaultAccessToken: process.env.VAULT_TOKEN as string,
323+
});
324+
325+
// Get the ERC20 contract
326+
const erc20Contract = getContract({
327+
// this ERC20 on arbitrumSepolia has infinite free public claim phase
328+
address: "0xd4d3D9261e2da56c4cC618a06dD5BDcB1A7a21d7",
329+
chain: arbitrumSepolia,
330+
client: TEST_CLIENT,
331+
});
332+
333+
// Check initial signer balance
334+
const initialSignerBalance = await getBalance({
335+
address: sessionKeyAccountAddress,
336+
contract: erc20Contract,
337+
});
338+
339+
// Claim 10 tokens to the smart account
340+
const claimTx = claimToERC20({
341+
contract: erc20Contract,
342+
to: smartAccount.address,
343+
quantity: "10",
344+
});
345+
346+
const claimResult = await sendTransaction({
347+
account: serverWallet,
348+
transaction: claimTx,
349+
});
350+
expect(claimResult).toBeDefined();
351+
352+
// Check balance after claim
353+
const balanceAfterClaim = await getBalance({
354+
address: smartAccount.address,
355+
contract: erc20Contract,
356+
});
357+
358+
// Verify the smart account now has 10 tokens (since it started with 0)
359+
expect(balanceAfterClaim.value).toBe(toWei("10"));
360+
361+
// Transfer tokens from smart account to signer
362+
const transferTx = transfer({
363+
contract: erc20Contract,
364+
to: sessionKeyAccountAddress,
365+
amount: "10",
366+
});
367+
368+
const transferResult = await sendTransaction({
369+
account: serverWallet,
370+
transaction: transferTx,
371+
});
372+
expect(transferResult).toBeDefined();
373+
374+
// Check final balances
375+
const finalSmartAccountBalance = await getBalance({
376+
address: smartAccount.address,
377+
contract: erc20Contract,
378+
});
379+
const finalSignerBalance = await getBalance({
380+
address: sessionKeyAccountAddress,
381+
contract: erc20Contract,
382+
});
383+
// Verify the transfer worked correctly
384+
// Smart account should be back to 0 balance
385+
expect(finalSmartAccountBalance.value).toBe(0n);
386+
// Signer should have gained 10 tokens
387+
expect(
388+
BigInt(finalSignerBalance.value) - BigInt(initialSignerBalance.value),
389+
).toBe(toWei("10"));
390+
});
275391
},
276392
);

packages/thirdweb/src/engine/server-wallet.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,18 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
266266
return data.transactions.map((t) => t.id);
267267
};
268268

269+
const getAddress = () => {
270+
if (
271+
executionOptions?.type === "ERC4337" &&
272+
executionOptions.smartAccountAddress
273+
) {
274+
return executionOptions.smartAccountAddress;
275+
}
276+
return address;
277+
};
278+
269279
return {
270-
address,
280+
address: getAddress(),
271281
enqueueBatchTransaction: async (args: {
272282
transactions: PreparedTransaction[];
273283
}) => {

0 commit comments

Comments
 (0)