diff --git a/.changeset/silver-weeks-rescue.md b/.changeset/silver-weeks-rescue.md new file mode 100644 index 00000000000..7663c299bca --- /dev/null +++ b/.changeset/silver-weeks-rescue.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Properly updates active smart wallet when switching signer account on EOA wallet diff --git a/apps/playground-web/src/app/api/airdrop/route.ts b/apps/playground-web/src/app/api/airdrop/route.ts index 6fca6a8e9c2..bec24c4ac7b 100644 --- a/apps/playground-web/src/app/api/airdrop/route.ts +++ b/apps/playground-web/src/app/api/airdrop/route.ts @@ -1,22 +1,10 @@ import { Engine } from "@thirdweb-dev/engine"; -import * as dotenv from "dotenv"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; -dotenv.config(); - const CHAIN_ID = "84532"; const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; -console.log("Environment Variables:"); -console.log("CHAIN_ID:", CHAIN_ID); -console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS); -console.log("ENGINE_URL:", process.env.ENGINE_URL); -console.log( - "ACCESS_TOKEN:", - process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set", -); - const engine = new Engine({ url: process.env.ENGINE_URL as string, accessToken: process.env.ENGINE_ACCESS_TOKEN as string, diff --git a/apps/playground-web/src/app/api/claimTo/route.ts b/apps/playground-web/src/app/api/claimTo/route.ts index 3bfe7476a3f..a732f87b56a 100644 --- a/apps/playground-web/src/app/api/claimTo/route.ts +++ b/apps/playground-web/src/app/api/claimTo/route.ts @@ -1,22 +1,10 @@ import { Engine } from "@thirdweb-dev/engine"; -import * as dotenv from "dotenv"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; -dotenv.config(); - const BASESEP_CHAIN_ID = "84532"; const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; -console.log("Environment Variables:"); -console.log("CHAIN_ID:", BASESEP_CHAIN_ID); -console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS); -console.log("ENGINE_URL:", process.env.ENGINE_URL); -console.log( - "ACCESS_TOKEN:", - process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set", -); - const engine = new Engine({ url: process.env.ENGINE_URL as string, accessToken: process.env.ENGINE_ACCESS_TOKEN as string, diff --git a/apps/playground-web/src/app/api/erc20BatchMintTo/route.ts b/apps/playground-web/src/app/api/erc20BatchMintTo/route.ts index 276bf896759..ce001c2cddc 100644 --- a/apps/playground-web/src/app/api/erc20BatchMintTo/route.ts +++ b/apps/playground-web/src/app/api/erc20BatchMintTo/route.ts @@ -1,23 +1,10 @@ import { Engine } from "@thirdweb-dev/engine"; -import * as dotenv from "dotenv"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import type { Address } from "thirdweb"; -dotenv.config(); - -const BASESEP_CHAIN_ID = "84532"; const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; -console.log("Environment Variables:"); -console.log("CHAIN_ID:", BASESEP_CHAIN_ID); -console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS); -console.log("ENGINE_URL:", process.env.ENGINE_URL); -console.log( - "ACCESS_TOKEN:", - process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set", -); - const engine = new Engine({ url: process.env.ENGINE_URL as string, accessToken: process.env.ENGINE_ACCESS_TOKEN as string, diff --git a/apps/playground-web/src/app/api/mintTo/route.ts b/apps/playground-web/src/app/api/mintTo/route.ts index b3f4767ac2b..4a68ae6338d 100644 --- a/apps/playground-web/src/app/api/mintTo/route.ts +++ b/apps/playground-web/src/app/api/mintTo/route.ts @@ -1,24 +1,10 @@ import { Engine } from "@thirdweb-dev/engine"; -import * as dotenv from "dotenv"; import { type NextRequest, NextResponse } from "next/server"; -dotenv.config(); - const CHAIN_ID = "84532"; const CONTRACT_ADDRESS = "0x8CD193648f5D4E8CD9fD0f8d3865052790A680f6"; const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; -// Add logging for environment variables -console.log("Environment Variables:"); -console.log("CHAIN_ID:", CHAIN_ID); -console.log("BACKEND_WALLET_ADDRESS:", BACKEND_WALLET_ADDRESS); -console.log("CONTRACT_ADDRESS:", CONTRACT_ADDRESS); -console.log("ENGINE_URL:", process.env.ENGINE_URL); -console.log( - "ACCESS_TOKEN:", - process.env.ENGINE_ACCESS_TOKEN ? "Set" : "Not Set", -); - const engine = new Engine({ url: process.env.ENGINE_URL as string, accessToken: process.env.ENGINE_ACCESS_TOKEN as string, diff --git a/biome.json b/biome.json index c899fa6d968..86371fcc3be 100644 --- a/biome.json +++ b/biome.json @@ -29,7 +29,8 @@ "noRestrictedGlobals": { "options": { "deniedGlobals": ["Buffer"] }, "level": "error" - } + }, + "noUselessElse": "off" } } }, diff --git a/packages/thirdweb/src/wallets/manager/connection-manager.test.ts b/packages/thirdweb/src/wallets/manager/connection-manager.test.ts new file mode 100644 index 00000000000..db0d5412ced --- /dev/null +++ b/packages/thirdweb/src/wallets/manager/connection-manager.test.ts @@ -0,0 +1,68 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js"; +import { sepolia } from "../../chains/chain-definitions/sepolia.js"; +import type { ThirdwebClient } from "../../client/client.js"; +import type { AsyncStorage } from "../../utils/storage/AsyncStorage.js"; +import type { Account, Wallet } from "../interfaces/wallet.js"; +import type { SmartWalletOptions } from "../smart/types.js"; +import { createConnectionManager } from "./index.js"; + +describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => { + let storage: AsyncStorage; + let client: ThirdwebClient; + let wallet: Wallet; + let account: Account; + let smartWalletOptions: SmartWalletOptions; + + beforeEach(() => { + storage = { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + }; + client = TEST_CLIENT; + account = TEST_ACCOUNT_A; + wallet = { + id: "wallet-id", + getAccount: vi.fn().mockReturnValue(account), + subscribe: vi.fn(), + disconnect: vi.fn(), + switchChain: vi.fn(), + getChain: vi.fn().mockReturnValue(sepolia), + getConfig: vi.fn(), + } as unknown as Wallet; + smartWalletOptions = { + chain: sepolia, + } as SmartWalletOptions; + }); + + it("connect should handle connection and call onConnect", async () => { + const manager = createConnectionManager(storage); + const onConnect = vi.fn(); + + await manager.connect(wallet, { client, onConnect }); + + expect(onConnect).toHaveBeenCalledWith(wallet); + expect(storage.setItem).toHaveBeenCalledWith(expect.any(String), wallet.id); + }); + + it("handleConnection should connect smart wallet", async () => { + const manager = createConnectionManager(storage); + + const smartWallet = await manager.handleConnection(wallet, { + client, + accountAbstraction: smartWalletOptions, + }); + + expect(manager.activeWalletStore.getValue()).toBe(smartWallet); + }); + + it("handleConnection should add wallet to connected wallets", async () => { + const manager = createConnectionManager(storage); + + await manager.handleConnection(wallet, { client }); + + expect(manager.connectedWallets.getValue()).toContain(wallet); + }); +}); diff --git a/packages/thirdweb/src/wallets/manager/index.ts b/packages/thirdweb/src/wallets/manager/index.ts index c121f54eaf9..fb52b794b9a 100644 --- a/packages/thirdweb/src/wallets/manager/index.ts +++ b/packages/thirdweb/src/wallets/manager/index.ts @@ -128,37 +128,56 @@ export function createConnectionManager(storage: AsyncStorage) { throw new Error("Can not set a wallet without an account as active"); } - const personalWallet = wallet; - let activeWallet = personalWallet; - const isInAppSmartAccount = hasSmartAccount(wallet); - if (options?.accountAbstraction && !isInAppSmartAccount) { - activeWallet = smartWallet(options.accountAbstraction); - await activeWallet.connect({ - personalAccount: wallet.getAccount(), - client: options.client, - }); - } + const activeWallet = await (async () => { + if (options?.accountAbstraction && !hasSmartAccount(wallet)) { + return await handleSmartWalletConnection( + account, + options.client, + options.accountAbstraction, + ); + } else { + return wallet; + } + })(); // add personal wallet to connected wallets list - addConnectedWallet(personalWallet); + addConnectedWallet(wallet); - if (personalWallet.id !== "smart") { - await storage.setItem(LAST_ACTIVE_EOA_ID, personalWallet.id); + if (wallet.id !== "smart") { + await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id); } + handleSetActiveWallet(activeWallet); + + wallet.subscribe("accountChanged", async () => { + // We reimplement connect here to prevent memory leaks + const newWallet = await handleConnection(wallet, options); + options?.onConnect?.(newWallet); + }); + return activeWallet; }; + const handleSmartWalletConnection = async ( + signer: Account, + client: ThirdwebClient, + options: SmartWalletOptions, + ) => { + const wallet = smartWallet(options); + + await wallet.connect({ + personalAccount: signer, + client: client, + chain: options.chain, + }); + + return wallet; + }; + const connect = async (wallet: Wallet, options?: ConnectManagerOptions) => { - // connectedWallet can be either wallet or smartWallet based on + // connectedWallet can be either wallet or smartWallet const connectedWallet = await handleConnection(wallet, options); options?.onConnect?.(connectedWallet); - handleSetActiveWallet(connectedWallet); - wallet.subscribe("accountChanged", async () => { - const newConnectedWallet = await handleConnection(wallet, options); - options?.onConnect?.(newConnectedWallet); - handleSetActiveWallet(newConnectedWallet); - }); return connectedWallet; };