diff --git a/packages/thirdweb/src/react/hooks/connection/useAutoConnect.ts b/packages/thirdweb/src/react/hooks/connection/useAutoConnect.ts index 7dc29bb537d..3469d3715cb 100644 --- a/packages/thirdweb/src/react/hooks/connection/useAutoConnect.ts +++ b/packages/thirdweb/src/react/hooks/connection/useAutoConnect.ts @@ -38,75 +38,82 @@ export function AutoConnect() { connectionManager.getStoredActiveWalletId(), ]); + // if no wallets were last connected if (!lastConnectedWalletIds) { setConnectionStatus("disconnected"); return; } - // connect the last active wallet first - const lastActiveWalletConfig = wallets.find( - (w) => w.metadata.id === lastActiveWalletId, - ); + async function handleWalletConnection(walletConfig: WalletConfig) { + // if this wallet requires a personal wallet to be connected + if (walletConfig.personalWalletConfigs) { + // get saved connection params for this wallet + const savedParams = await getSavedConnectParamsFromStorage( + walletConfig.metadata.id, + ); + + // if must be an object with `personalWalletId` property + if (!isValidWithPersonalWalletConnectionOptions(savedParams)) { + throw new Error("Invalid connection params"); + } - // connect the active wallet and set it as active - if (lastActiveWalletConfig) { - // ------------ TRY ------------ - try { - // if this wallet requires a personal wallet to be connected - if (lastActiveWalletConfig.personalWalletConfigs) { - // get saved connection params for this wallet - const savedParams = await getSavedConnectParamsFromStorage( - lastActiveWalletConfig.metadata.id, - ); - - // if must be an object with `personalWalletId` property - if (!isValidWithPersonalWalletConnectionOptions(savedParams)) { - throw new Error("Invalid connection params"); - } - - // find the personal wallet config - const personalWalletConfig = - lastActiveWalletConfig.personalWalletConfigs.find( - (w) => w.metadata.id === savedParams.personalWalletId, - ); - - if (!personalWalletConfig) { - throw new Error("Personal wallet not found"); - } - - // create and auto connect the personal wallet to get personal account - const personalWallet = personalWalletConfig.create({ - client, - dappMetadata, - }); - - const personalAccount = await personalWallet.autoConnect(); - - // create wallet - const wallet = lastActiveWalletConfig.create({ - client, - dappMetadata, - }) as WalletWithPersonalAccount; - - // auto connect the wallet using the personal account - const account = await wallet.autoConnect({ - personalAccount, - }); + // find the personal wallet config + const personalWalletConfig = walletConfig.personalWalletConfigs.find( + (w) => w.metadata.id === savedParams.personalWalletId, + ); - connect(account); + if (!personalWalletConfig) { + throw new Error("Personal wallet not found"); } - // if this wallet does not require a personal wallet to be connected - else { - const wallet = lastActiveWalletConfig.create({ - client, - dappMetadata, - }); - const account = await wallet.autoConnect(); + // create and auto connect the personal wallet to get personal account + const personalWallet = personalWalletConfig.create({ + client, + dappMetadata, + }); + + const personalAccount = await personalWallet.autoConnect(); + + // create wallet + const wallet = walletConfig.create({ + client, + dappMetadata, + }) as WalletWithPersonalAccount; + + // auto connect the wallet using the personal account + const account = await wallet.autoConnect({ + personalAccount, + }); + + return account; + } + + // if this wallet does not require a personal wallet to be connected + else { + const wallet = walletConfig.create({ + client, + dappMetadata, + }); + const account = await wallet.autoConnect(); + return account; + } + } + + // connect the last active wallet and set it as active + const activeWalletConfig = wallets.find( + (w) => w.metadata.id === lastActiveWalletId, + ); + + if (activeWalletConfig) { + try { + const account = await handleWalletConnection(activeWalletConfig); + if (account) { connect(account); + } else { + setConnectionStatus("disconnected"); } } catch (e) { - console.log("failed to auto connect last active wallet"); + console.error("Failed to auto connect last active wallet"); console.error(e); setConnectionStatus("disconnected"); } @@ -114,24 +121,18 @@ export function AutoConnect() { setConnectionStatus("disconnected"); } - const otherWalletConfigs: WalletConfig[] = []; - wallets.forEach((w) => { - if (w.metadata.id === lastActiveWalletId) { - return; - } - if (lastConnectedWalletIds.includes(w.metadata.id)) { - otherWalletConfigs.push(w); - } - }); + // then connect wallets that were last connected but were not set as active + const otherWallets = wallets.filter( + (w) => + w.metadata.id !== lastActiveWalletId && + lastConnectedWalletIds.includes(w.metadata.id), + ); - // connect other wallets - otherWalletConfigs.forEach(async (config) => { - const wallet = config.create({ - client, - dappMetadata, - }); - const account = await wallet.autoConnect(); - connectionManager.setConnectedAccount(account); + otherWallets.forEach(async (config) => { + const account = await handleWalletConnection(config); + if (account) { + connectionManager.setConnectedAccount(account); + } }); }; diff --git a/packages/thirdweb/src/react/ui/ConnectWallet/Details.tsx b/packages/thirdweb/src/react/ui/ConnectWallet/Details.tsx index a2ecd868a36..13722ed9d6a 100644 --- a/packages/thirdweb/src/react/ui/ConnectWallet/Details.tsx +++ b/packages/thirdweb/src/react/ui/ConnectWallet/Details.tsx @@ -2,6 +2,7 @@ import { ExitIcon, ChevronRightIcon, TextAlignJustifyIcon, + EnterIcon, } from "@radix-ui/react-icons"; import styled from "@emotion/styled"; import { useState, useEffect } from "react"; @@ -9,6 +10,7 @@ import { useActiveAccount, useActiveWalletChainId, useDisconnect, + useSetActiveAccount, } from "../../providers/wallet-provider.js"; import { useTWLocale } from "../../providers/locale-provider.js"; import { Modal } from "../components/Modal.js"; @@ -43,6 +45,12 @@ import type { ConnectWallet_DetailsButtonOptions, ConnectWallet_DetailsModalOptions, } from "./ConnectWalletProps.js"; +import { + smartWalletMetadata, + type Account, + type WalletWithPersonalAccount, + personalAccountToSmartAccountMap, +} from "../../../wallets/index.js"; // import { walletIds } from "../../../wallets/walletIds.js"; // TEMP @@ -89,6 +97,9 @@ export const ConnectedWalletDetails: React.FC<{ account: activeAccount, }); + const [screen, setScreen] = useState("main"); + const [isOpen, setIsOpen] = useState(false); + // const activeWalletConfig = undefined; // const activeWalletConfig = useWalletConfig(); // const ensQuery = useENS(); @@ -103,19 +114,14 @@ export const ConnectedWalletDetails: React.FC<{ // string | undefined // >(undefined); - const [screen, setScreen] = useState("main"); - const [isOpen, setIsOpen] = useState(false); - // const sdk = useSDK(); - // const personalWallet = activeWallet?.getPersonalWallet() as - // | WalletInstance - // | undefined; + const personalAccount = (activeAccount?.wallet as WalletWithPersonalAccount) + ?.personalAccount; - // const personalWalletConfig = - // personalWallet && walletContext.getWalletConfig(personalWallet); - // const wrapperWalletConfig = - // wrapperWallet && walletContext.getWalletConfig(wrapperWallet); + const smartAccount = activeAccount + ? personalAccountToSmartAccountMap.get(activeAccount) + : undefined; const disableSwitchChain = !activeAccount?.wallet.switchChain; @@ -171,7 +177,11 @@ export const ConnectedWalletDetails: React.FC<{ // const walletIconUrl = // overrideWalletIconUrl || activeWalletConfig?.meta.iconURL || ""; // const avatarOrWalletIconUrl = avatarUrl || walletIconUrl; - const avatarOrWalletIconUrl = activeAccount?.wallet.metadata.iconUrl || ""; + let avatarOrWalletIconUrl = activeAccount?.wallet.metadata.iconUrl || ""; + + if (activeAccount && "isSmartWallet" in activeAccount.wallet) { + avatarOrWalletIconUrl = smartWalletMetadata.iconUrl; + } const trigger = props.detailsButton?.render ? (
@@ -399,27 +409,19 @@ export const ConnectedWalletDetails: React.FC<{ > {networkSwitcherButton} - {/* Switch to Personal Wallet for Safe */} - {/* {personalWallet && - personalWalletConfig && - !props.hideSwitchToPersonalWallet && ( - - )} */} + )} - {/* Switch to Wrapper Wallet */} - {/* {wrapperWalletConfig && wrapperWallet && ( - - )} */} + {/* Switch to Smart Wallet */} + {smartAccount && ( + + )} {/* Switch Account for Metamask */} {/* {isActuallyMetaMask && @@ -710,33 +712,33 @@ export const StyledChevronRightIcon = /* @__PURE__ */ styled( }; }); -// function WalletSwitcher({ -// wallet, -// name, -// }: { -// wallet: WalletInstance; -// name: string; -// }) { -// const walletContext = useWalletContext(); -// const locale = useTWLocale().connectWallet; +function AccountSwitcher({ + account, + name, +}: { + account: Account; + name: string; +}) { + const setActiveAccount = useSetActiveAccount(); + const locale = useTWLocale().connectWallet; -// return ( -// { -// walletContext.setConnectedWallet(wallet); -// }} -// style={{ -// fontSize: fontSize.sm, -// }} -// > -// -// -// {locale.switchTo} {name} -// -// -// ); -// } + return ( + { + setActiveAccount(account); + }} + style={{ + fontSize: fontSize.sm, + }} + > + + + {locale.switchTo} {name} + + + ); +} // const ActiveDot = /* @__PURE__ */ StyledDiv(() => { // const theme = useCustomTheme(); diff --git a/packages/thirdweb/src/wallets/index.ts b/packages/thirdweb/src/wallets/index.ts index df62c2a04d2..8b788e1d964 100644 --- a/packages/thirdweb/src/wallets/index.ts +++ b/packages/thirdweb/src/wallets/index.ts @@ -69,6 +69,7 @@ export { smartWallet, SmartWallet, smartWalletMetadata, + personalAccountToSmartAccountMap, } from "./smart/index.js"; export type { SmartWalletOptions } from "./smart/types.js"; diff --git a/packages/thirdweb/src/wallets/smart/index.ts b/packages/thirdweb/src/wallets/smart/index.ts index abfc1c136bf..e3a540d2e7d 100644 --- a/packages/thirdweb/src/wallets/smart/index.ts +++ b/packages/thirdweb/src/wallets/smart/index.ts @@ -43,6 +43,12 @@ export const smartWalletMetadata = { "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiByeD0iMTIiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8xXzkxKSIvPgo8cGF0aCBkPSJNMzkuOTk2OSAxOEw0MC4yMzMzIDE4LjAxNTZMNDAuMzUxMyAxOC4wMzMzTDQwLjQ3MzYgMTguMDYyMkw0MC42OTU4IDE4LjEzNzhDNDAuODQ5NCAxOC4yMDA2IDQwLjk5NTQgMTguMjg0MiA0MS4xMzA1IDE4LjM4NjdMNDEuMzM4NyAxOC41Njg5TDQxLjg0OTMgMTkuMDUzM0M0NS44ODkxIDIyLjc3NjggNTAuOTk0NyAyNC43NzYyIDU2LjI0NTggMjQuNjkxMUw1Ni45MzA3IDI0LjY2ODlDNTcuMzc4MyAyNC42NDYyIDU3LjgyIDI0Ljc5MDkgNTguMTg0OSAyNS4wNzk4QzU4LjU0OTggMjUuMzY4NyA1OC44MTY3IDI1Ljc4NSA1OC45NDMxIDI2LjI2MjJDNTkuOTI3MSAyOS45NzY5IDYwLjIyODMgMzMuODczMSA1OS44Mjg3IDM3LjcxOTRDNTkuNDI4OSA0MS41NjU4IDU4LjMzNjcgNDUuMjgzNiA1Ni42MTY1IDQ4LjY1MjNDNTQuODk2NSA1Mi4wMjA3IDUyLjU4MzYgNTQuOTcxNCA0OS44MTU2IDU3LjMyODVDNDcuMDQ3NiA1OS42ODU2IDQzLjg4MDkgNjEuNDAxMiA0MC41MDM2IDYyLjM3MzRDNDAuMTc0IDYyLjQ2ODMgMzkuODI4IDYyLjQ2ODMgMzkuNDk4MiA2Mi4zNzM0QzM2LjEyMDkgNjEuNDAxNCAzMi45NTM4IDU5LjY4NiAzMC4xODU2IDU3LjMyODlDMjcuNDE3NiA1NC45NzE4IDI1LjEwNDUgNTIuMDIxNCAyMy4zODQyIDQ4LjY1MjlDMjEuNjYzOCA0NS4yODQzIDIwLjU3MTMgNDEuNTY2MiAyMC4xNzE1IDM3LjcxOThDMTkuNzcxNyAzMy44NzM0IDIwLjA3MjcgMjkuOTc2OSAyMS4wNTY3IDI2LjI2MjJDMjEuMTgzMSAyNS43ODUgMjEuNDUwMSAyNS4zNjg3IDIxLjgxNSAyNS4wNzk4QzIyLjE3OTkgMjQuNzkwOSAyMi42MjE1IDI0LjY0NjIgMjMuMDY5MyAyNC42Njg5QzI4LjU1MTMgMjQuOTQ3IDMzLjkyOTMgMjIuOTQ0NCAzOC4xNTA3IDE5LjA1MzNMMzguNjc3MyAxOC41NTMzTDM4Ljg2OTYgMTguMzg2N0MzOS4wMDQ3IDE4LjI4NDIgMzkuMTUwNSAxOC4yMDA2IDM5LjMwNCAxOC4xMzc4TDM5LjUyODIgMTguMDYyMkMzOS42MDY5IDE4LjA0MTIgMzkuNjg2NSAxOC4wMjU2IDM5Ljc2NjcgMTguMDE1NkwzOS45OTY5IDE4Wk00MC4wMDA5IDMzLjU1NTZDMzguOTkwNSAzMy41NTUxIDM4LjAxNzMgMzMuOTc4NyAzNy4yNzY1IDM0Ljc0MTFDMzYuNTM1NiAzNS41MDM2IDM2LjA4MTYgMzYuNTQ4NSAzNi4wMDU4IDM3LjY2NjdMMzUuOTk1OCAzOEwzNi4wMDU4IDM4LjMzMzRDMzYuMDU1MSAzOS4wNTQ3IDM2LjI2MjUgMzkuNzUxOCAzNi42MDk4IDQwLjM2NEMzNi45NTY5IDQwLjk3NjIgMzcuNDMzNiA0MS40ODUxIDM3Ljk5ODUgNDEuODQ2N1Y0NS43Nzc4TDM4LjAxMjUgNDYuMDM3OEMzOC4wNzI3IDQ2LjYwMDMgMzguMzI0MiA0Ny4xMTU4IDM4LjcxNTYgNDcuNDc5NEMzOS4xMDcxIDQ3Ljg0MjkgMzkuNjA4NyA0OC4wMjY5IDQwLjExODIgNDcuOTkzOEM0MC42Mjc4IDQ3Ljk2MDUgNDEuMTA2NyA0Ny43MTI3IDQxLjQ1NzEgNDcuMzAwOUM0MS44MDc2IDQ2Ljg4ODkgNDIuMDAyOSA0Ni4zNDQzIDQyLjAwMzYgNDUuNzc3OEw0Mi4wMDU2IDQxLjg0ODlDNDIuNzY5MSA0MS4zNTk2IDQzLjM2NiA0MC42MDQyIDQzLjcwMzQgMzkuNzAwMkM0NC4wNDA3IDM4Ljc5NiA0NC4wOTk2IDM3Ljc5MzggNDMuODcxMSAzNi44NDg3QzQzLjY0MjcgMzUuOTAzNiA0My4xMzk2IDM1LjA2ODUgNDIuNDM5OCAzNC40NzMxQzQxLjc0IDMzLjg3NzYgNDAuODgyNyAzMy41NTUxIDQwLjAwMDkgMzMuNTU1NloiIGZpbGw9InVybCgjcGFpbnQxX2xpbmVhcl8xXzkxKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzFfOTEiIHgxPSI0MCIgeTE9IjAiIHgyPSI0MCIgeTI9IjgwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM4MzU2QkQiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjN0MyMEY0Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8xXzkxIiB4MT0iNDAiIHkxPSIxOCIgeDI9IjQwIiB5Mj0iNjIuNDQ0NSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNFMUQ4RkIiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K", }; +/** + * We can get the personal account for given smart account but not the other way around - this map gives us the reverse lookup + * @internal + */ +export const personalAccountToSmartAccountMap = new WeakMap(); + /** * */ @@ -51,6 +57,7 @@ export class SmartWallet implements WalletWithPersonalAccount { personalAccount: Account | undefined; metadata: Wallet["metadata"]; chainId?: bigint | undefined; + isSmartWallet: true; /** * Create an instance of the SmartWallet. @@ -63,6 +70,7 @@ export class SmartWallet implements WalletWithPersonalAccount { constructor(options: SmartWalletOptions) { this.options = options; this.metadata = options.metadata || smartWalletMetadata; + this.isSmartWallet = true; } /** @@ -98,7 +106,19 @@ export class SmartWallet implements WalletWithPersonalAccount { saveConnectParamsToStorage(this.metadata.id, paramsToSave); - return smartAccount(this, { ...this.options, ...connectionOptions }); + // TODO: listen for chainChanged event on the personal wallet and emit the disconnect event on the smart wallet + + const account = await smartAccount(this, { + ...this.options, + ...connectionOptions, + }); + + personalAccountToSmartAccountMap.set( + connectionOptions.personalAccount, + account, + ); + + return account; } /**