Skip to content

Commit bca24a0

Browse files
committed
fix(sdk): disconnect smart wallet when signer is disconnected
changeset
1 parent 9d8ecce commit bca24a0

File tree

7 files changed

+77
-15
lines changed

7 files changed

+77
-15
lines changed

.changeset/strong-meals-remain.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: Disconnect smart account when account signer is disconnected

packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,13 @@ function DetailsModal(props: {
343343
props.onDisconnect(info);
344344
}
345345

346+
useEffect(() => {
347+
if (!activeAccount) {
348+
setIsOpen(false);
349+
props.closeModal();
350+
}
351+
}, [activeAccount, props.closeModal]);
352+
346353
const networkSwitcherButton = (
347354
<MenuButton
348355
type="button"
@@ -993,12 +1000,11 @@ function DetailsModal(props: {
9931000
}
9941001
}}
9951002
>
996-
<AccountProvider
997-
address={activeAccount?.address || ""}
998-
client={client}
999-
>
1000-
{content}
1001-
</AccountProvider>
1003+
{activeAccount?.address && (
1004+
<AccountProvider address={activeAccount.address} client={client}>
1005+
{content}
1006+
</AccountProvider>
1007+
)}
10021008
</Modal>
10031009
</ScreenSetupContext.Provider>
10041010
</WalletUIStatesProvider>

packages/thirdweb/src/react/web/ui/prebuilt/Account/provider.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,20 @@ describe.runIf(process.env.TW_SECRET_KEY)("AccountProvider component", () => {
3535
}),
3636
).toBeInTheDocument();
3737
});
38+
39+
it("should throw an error if no address is provided", () => {
40+
expect(() => {
41+
render(
42+
<AccountProvider
43+
// biome-ignore lint/suspicious/noExplicitAny: testing invalid input
44+
address={undefined as any}
45+
client={TEST_CLIENT}
46+
>
47+
<AccountAddress />
48+
</AccountProvider>,
49+
);
50+
}).toThrowError(
51+
"AccountProvider: No address passed. Ensure an address is always provided to the AccountProvider",
52+
);
53+
});
3854
});

packages/thirdweb/src/react/web/ui/prebuilt/Account/provider.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ const AccountProviderContext = /* @__PURE__ */ createContext<
4848
export function AccountProvider(
4949
props: React.PropsWithChildren<AccountProviderProps>,
5050
) {
51+
if (!props.address) {
52+
throw new Error(
53+
"AccountProvider: No address passed. Ensure an address is always provided to the AccountProvider",
54+
);
55+
}
5156
return (
5257
<AccountProviderContext.Provider value={props}>
5358
{props.children}

packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export class IFrameWallet implements IWebWallet {
316316
parsedTypedData.types as SignerProcedureTypes["signTypedDataV4"]["types"],
317317
message:
318318
parsedTypedData.message as SignerProcedureTypes["signTypedDataV4"]["message"],
319-
chainId: chainId || 1,
319+
chainId: Number(chainId) || 1,
320320
partnerId,
321321
rpcEndpoint: `https://${chainId}.${RPC_URL}`, // TODO (ew) shouldnt be needed
322322
},

packages/thirdweb/src/wallets/manager/connection-manager.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
4444
await manager.connect(wallet, { client, onConnect });
4545

4646
expect(onConnect).toHaveBeenCalledWith(wallet);
47-
expect(storage.setItem).toHaveBeenCalledWith(expect.any(String), wallet.id);
47+
expect(storage.setItem).toHaveBeenCalled();
4848
});
4949

5050
it("handleConnection should connect smart wallet", async () => {
@@ -65,4 +65,22 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
6565

6666
expect(manager.connectedWallets.getValue()).toContain(wallet);
6767
});
68+
69+
it("should disconnect the active wallet when the EOA disconnects", async () => {
70+
const manager = createConnectionManager(storage);
71+
72+
const smartWallet = await manager.handleConnection(wallet, {
73+
client,
74+
accountAbstraction: smartWalletOptions,
75+
});
76+
77+
expect(manager.activeWalletStore.getValue()).toBe(smartWallet);
78+
79+
await wallet.disconnect();
80+
81+
expect(wallet.subscribe).toHaveBeenCalledWith(
82+
"disconnect",
83+
expect.any(Function),
84+
);
85+
});
6886
});

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export function createConnectionManager(storage: AsyncStorage) {
131131
const activeWallet = await (async () => {
132132
if (options?.accountAbstraction && !hasSmartAccount(wallet)) {
133133
return await handleSmartWalletConnection(
134-
account,
134+
wallet,
135135
options.client,
136136
options.accountAbstraction,
137137
);
@@ -140,12 +140,10 @@ export function createConnectionManager(storage: AsyncStorage) {
140140
}
141141
})();
142142

143-
// add personal wallet to connected wallets list
144-
addConnectedWallet(wallet);
143+
await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id);
145144

146-
if (wallet.id !== "smart") {
147-
await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id);
148-
}
145+
// add personal wallet to connected wallets list even if it's not the active one
146+
addConnectedWallet(wallet);
149147

150148
handleSetActiveWallet(activeWallet);
151149

@@ -159,10 +157,15 @@ export function createConnectionManager(storage: AsyncStorage) {
159157
};
160158

161159
const handleSmartWalletConnection = async (
162-
signer: Account,
160+
eoaWallet: Wallet,
163161
client: ThirdwebClient,
164162
options: SmartWalletOptions,
165163
) => {
164+
const signer = eoaWallet.getAccount();
165+
if (!signer) {
166+
throw new Error("Can not set a wallet without an account as active");
167+
}
168+
166169
const wallet = smartWallet(options);
167170

168171
await wallet.connect({
@@ -171,6 +174,15 @@ export function createConnectionManager(storage: AsyncStorage) {
171174
chain: options.chain,
172175
});
173176

177+
// Disconnect the active wallet when the EOA disconnects if it the active wallet is a smart wallet
178+
const disconnectUnsub = eoaWallet.subscribe("disconnect", () => {
179+
handleDisconnect();
180+
});
181+
const handleDisconnect = () => {
182+
disconnectUnsub();
183+
onWalletDisconnect(wallet);
184+
};
185+
174186
return wallet;
175187
};
176188

0 commit comments

Comments
 (0)