Skip to content

Commit c3d7b66

Browse files
committed
[SDK] Fix: Account switching with connected smart wallets (#5592)
Fixes #5537 <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on improving wallet connection handling in the `thirdweb` project, specifically when switching signer accounts and simplifying connection logic for smart wallets. ### Detailed summary - Updated `biome.json` to add `"noUselessElse": "off"`. - Modified wallet connection logic in `packages/thirdweb/src/wallets/manager/index.ts` for better handling of active wallets. - Added tests for connection management in `packages/thirdweb/src/wallets/manager/connection-manager.test.ts`. - Removed environment variable logging across multiple API routes. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent ea855c6 commit c3d7b66

File tree

8 files changed

+114
-72
lines changed

8 files changed

+114
-72
lines changed

.changeset/silver-weeks-rescue.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Properly updates active smart wallet when switching signer account on EOA wallet

apps/playground-web/src/app/api/airdrop/route.ts

-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
import { Engine } from "@thirdweb-dev/engine";
2-
import * as dotenv from "dotenv";
32
import type { NextRequest } from "next/server";
43
import { NextResponse } from "next/server";
54

6-
dotenv.config();
7-
85
const CHAIN_ID = "84532";
96
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
107

11-
console.log("Environment Variables:");
12-
console.log("CHAIN_ID:", CHAIN_ID);
13-
console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS);
14-
console.log("ENGINE_URL:", process.env.ENGINE_URL);
15-
console.log(
16-
"ACCESS_TOKEN:",
17-
process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set",
18-
);
19-
208
const engine = new Engine({
219
url: process.env.ENGINE_URL as string,
2210
accessToken: process.env.ENGINE_ACCESS_TOKEN as string,

apps/playground-web/src/app/api/claimTo/route.ts

-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
import { Engine } from "@thirdweb-dev/engine";
2-
import * as dotenv from "dotenv";
32
import type { NextRequest } from "next/server";
43
import { NextResponse } from "next/server";
54

6-
dotenv.config();
7-
85
const BASESEP_CHAIN_ID = "84532";
96
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
107

11-
console.log("Environment Variables:");
12-
console.log("CHAIN_ID:", BASESEP_CHAIN_ID);
13-
console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS);
14-
console.log("ENGINE_URL:", process.env.ENGINE_URL);
15-
console.log(
16-
"ACCESS_TOKEN:",
17-
process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set",
18-
);
19-
208
const engine = new Engine({
219
url: process.env.ENGINE_URL as string,
2210
accessToken: process.env.ENGINE_ACCESS_TOKEN as string,

apps/playground-web/src/app/api/erc20BatchMintTo/route.ts

-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
11
import { Engine } from "@thirdweb-dev/engine";
2-
import * as dotenv from "dotenv";
32
import type { NextRequest } from "next/server";
43
import { NextResponse } from "next/server";
54
import type { Address } from "thirdweb";
65

7-
dotenv.config();
8-
9-
const BASESEP_CHAIN_ID = "84532";
106
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
117

12-
console.log("Environment Variables:");
13-
console.log("CHAIN_ID:", BASESEP_CHAIN_ID);
14-
console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS);
15-
console.log("ENGINE_URL:", process.env.ENGINE_URL);
16-
console.log(
17-
"ACCESS_TOKEN:",
18-
process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set",
19-
);
20-
218
const engine = new Engine({
229
url: process.env.ENGINE_URL as string,
2310
accessToken: process.env.ENGINE_ACCESS_TOKEN as string,

apps/playground-web/src/app/api/mintTo/route.ts

-14
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
import { Engine } from "@thirdweb-dev/engine";
2-
import * as dotenv from "dotenv";
32
import { type NextRequest, NextResponse } from "next/server";
43

5-
dotenv.config();
6-
74
const CHAIN_ID = "84532";
85
const CONTRACT_ADDRESS = "0x8CD193648f5D4E8CD9fD0f8d3865052790A680f6";
96
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
107

11-
// Add logging for environment variables
12-
console.log("Environment Variables:");
13-
console.log("CHAIN_ID:", CHAIN_ID);
14-
console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS);
15-
console.log("CONTRACT_ADDRESS:", CONTRACT_ADDRESS);
16-
console.log("ENGINE_URL:", process.env.ENGINE_URL);
17-
console.log(
18-
"ACCESS_TOKEN:",
19-
process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set",
20-
);
21-
228
const engine = new Engine({
239
url: process.env.ENGINE_URL as string,
2410
accessToken: process.env.ENGINE_ACCESS_TOKEN as string,

biome.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"noRestrictedGlobals": {
3030
"options": { "deniedGlobals": ["Buffer"] },
3131
"level": "error"
32-
}
32+
},
33+
"noUselessElse": "off"
3334
}
3435
}
3536
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
3+
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
4+
import { sepolia } from "../../chains/chain-definitions/sepolia.js";
5+
import type { ThirdwebClient } from "../../client/client.js";
6+
import type { AsyncStorage } from "../../utils/storage/AsyncStorage.js";
7+
import type { Account, Wallet } from "../interfaces/wallet.js";
8+
import type { SmartWalletOptions } from "../smart/types.js";
9+
import { createConnectionManager } from "./index.js";
10+
11+
describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
12+
let storage: AsyncStorage;
13+
let client: ThirdwebClient;
14+
let wallet: Wallet;
15+
let account: Account;
16+
let smartWalletOptions: SmartWalletOptions;
17+
18+
beforeEach(() => {
19+
storage = {
20+
getItem: vi.fn(),
21+
setItem: vi.fn(),
22+
removeItem: vi.fn(),
23+
};
24+
client = TEST_CLIENT;
25+
account = TEST_ACCOUNT_A;
26+
wallet = {
27+
id: "wallet-id",
28+
getAccount: vi.fn().mockReturnValue(account),
29+
subscribe: vi.fn(),
30+
disconnect: vi.fn(),
31+
switchChain: vi.fn(),
32+
getChain: vi.fn().mockReturnValue(sepolia),
33+
getConfig: vi.fn(),
34+
} as unknown as Wallet;
35+
smartWalletOptions = {
36+
chain: sepolia,
37+
} as SmartWalletOptions;
38+
});
39+
40+
it("connect should handle connection and call onConnect", async () => {
41+
const manager = createConnectionManager(storage);
42+
const onConnect = vi.fn();
43+
44+
await manager.connect(wallet, { client, onConnect });
45+
46+
expect(onConnect).toHaveBeenCalledWith(wallet);
47+
expect(storage.setItem).toHaveBeenCalledWith(expect.any(String), wallet.id);
48+
});
49+
50+
it("handleConnection should connect smart wallet", async () => {
51+
const manager = createConnectionManager(storage);
52+
53+
const smartWallet = await manager.handleConnection(wallet, {
54+
client,
55+
accountAbstraction: smartWalletOptions,
56+
});
57+
58+
expect(manager.activeWalletStore.getValue()).toBe(smartWallet);
59+
});
60+
61+
it("handleConnection should add wallet to connected wallets", async () => {
62+
const manager = createConnectionManager(storage);
63+
64+
await manager.handleConnection(wallet, { client });
65+
66+
expect(manager.connectedWallets.getValue()).toContain(wallet);
67+
});
68+
});

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

+39-20
Original file line numberDiff line numberDiff line change
@@ -128,37 +128,56 @@ export function createConnectionManager(storage: AsyncStorage) {
128128
throw new Error("Can not set a wallet without an account as active");
129129
}
130130

131-
const personalWallet = wallet;
132-
let activeWallet = personalWallet;
133-
const isInAppSmartAccount = hasSmartAccount(wallet);
134-
if (options?.accountAbstraction && !isInAppSmartAccount) {
135-
activeWallet = smartWallet(options.accountAbstraction);
136-
await activeWallet.connect({
137-
personalAccount: wallet.getAccount(),
138-
client: options.client,
139-
});
140-
}
131+
const activeWallet = await (async () => {
132+
if (options?.accountAbstraction && !hasSmartAccount(wallet)) {
133+
return await handleSmartWalletConnection(
134+
account,
135+
options.client,
136+
options.accountAbstraction,
137+
);
138+
} else {
139+
return wallet;
140+
}
141+
})();
141142

142143
// add personal wallet to connected wallets list
143-
addConnectedWallet(personalWallet);
144+
addConnectedWallet(wallet);
144145

145-
if (personalWallet.id !== "smart") {
146-
await storage.setItem(LAST_ACTIVE_EOA_ID, personalWallet.id);
146+
if (wallet.id !== "smart") {
147+
await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id);
147148
}
148149

150+
handleSetActiveWallet(activeWallet);
151+
152+
wallet.subscribe("accountChanged", async () => {
153+
// We reimplement connect here to prevent memory leaks
154+
const newWallet = await handleConnection(wallet, options);
155+
options?.onConnect?.(newWallet);
156+
});
157+
149158
return activeWallet;
150159
};
151160

161+
const handleSmartWalletConnection = async (
162+
signer: Account,
163+
client: ThirdwebClient,
164+
options: SmartWalletOptions,
165+
) => {
166+
const wallet = smartWallet(options);
167+
168+
await wallet.connect({
169+
personalAccount: signer,
170+
client: client,
171+
chain: options.chain,
172+
});
173+
174+
return wallet;
175+
};
176+
152177
const connect = async (wallet: Wallet, options?: ConnectManagerOptions) => {
153-
// connectedWallet can be either wallet or smartWallet based on
178+
// connectedWallet can be either wallet or smartWallet
154179
const connectedWallet = await handleConnection(wallet, options);
155180
options?.onConnect?.(connectedWallet);
156-
handleSetActiveWallet(connectedWallet);
157-
wallet.subscribe("accountChanged", async () => {
158-
const newConnectedWallet = await handleConnection(wallet, options);
159-
options?.onConnect?.(newConnectedWallet);
160-
handleSetActiveWallet(newConnectedWallet);
161-
});
162181
return connectedWallet;
163182
};
164183

0 commit comments

Comments
 (0)