From 37ba1fd67372565e1a1f5fc7438a950cdc78e4dc Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Wed, 20 May 2026 13:27:34 +0200 Subject: [PATCH 1/2] fix: v2 apis --- .../Components/AccountSelector.tsx | 11 +- .../Components/ProfileCard/ProfileCard.tsx | 3 - .../Contents/Profile/ProfileContent.tsx | 73 +++-- .../SelectWallet/SelectWalletContent.tsx | 173 ++++++++++-- .../src/components/common/AccountAvatar.tsx | 19 +- .../src/components/common/AddressDisplay.tsx | 52 +--- .../api/vetDomains/useGetAvatarOfAddress.ts | 6 +- .../hooks/api/vetDomains/useVechainDomain.ts | 13 +- .../src/hooks/api/wallet/useSwitchWallet.ts | 23 +- .../src/hooks/api/wallet/useWallet.ts | 267 +++++++++++++++++- .../src/hooks/api/wallet/useWalletStorage.ts | 35 ++- .../login/useConnectWithDappKitSource.ts | 24 +- packages/vechain-kit/src/languages/en.json | 3 +- 13 files changed, 558 insertions(+), 144 deletions(-) diff --git a/packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx b/packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx index 7227b08a..0775975a 100644 --- a/packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx +++ b/packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx @@ -21,7 +21,7 @@ import { AccountAvatar } from '@/components/common'; import { useState } from 'react'; import { AccountModalContentTypes } from '../Types/Types'; import { useTranslation } from 'react-i18next'; -import { useWallet, useSwitchWallet, useDAppKitWallet } from '@/hooks'; +import { useSwitchWallet } from '@/hooks'; type Props = { wallet: Wallet; @@ -45,9 +45,8 @@ export const AccountSelector = ({ style, }: Props) => { const { t } = useTranslation(); - const { connection } = useWallet(); - const { switchWallet, isSwitching, isInAppBrowser } = useSwitchWallet(); - const { isSwitchWalletEnabled } = useDAppKitWallet(); + const { switchWallet, isSwitching, isInAppBrowser, canSwitchWallet } = + useSwitchWallet(); const [copied, setCopied] = useState(false); @@ -125,9 +124,7 @@ export const AccountSelector = ({ - {(connection.isInAppBrowser && isSwitchWalletEnabled) || - (!connection.isInAppBrowser && - connection.isConnectedWithDappKit) ? ( + {canSwitchWallet ? ( } diff --git a/packages/vechain-kit/src/components/AccountModal/Contents/Profile/Components/ProfileCard/ProfileCard.tsx b/packages/vechain-kit/src/components/AccountModal/Contents/Profile/Components/ProfileCard/ProfileCard.tsx index a26215e2..b091251e 100644 --- a/packages/vechain-kit/src/components/AccountModal/Contents/Profile/Components/ProfileCard/ProfileCard.tsx +++ b/packages/vechain-kit/src/components/AccountModal/Contents/Profile/Components/ProfileCard/ProfileCard.tsx @@ -18,7 +18,6 @@ import { AccountModalContentTypes } from '@/components/AccountModal/Types'; export type ProfileCardProps = { address: string; onEditClick?: () => void; - onLogout?: () => void; showHeader?: boolean; showLinks?: boolean; showDescription?: boolean; @@ -39,7 +38,6 @@ export const ProfileCard = ({ showDisplayName = true, reserveNameDescriptionSpace = false, setCurrentContent, - onLogout, }: ProfileCardProps) => { const { network } = useVeChainKitConfig(); @@ -209,7 +207,6 @@ export const ProfileCard = ({ )} { const { t } = useTranslation(); - const { account, disconnect, connection } = useWallet(); - const { switchWallet, isSwitching, isInAppBrowser } = useSwitchWallet(); - const { isSwitchWalletEnabled } = useDAppKitWallet(); + const { account, disconnect } = useWallet(); + const { switchWallet, isSwitching, isInAppBrowser, canSwitchWallet } = + useSwitchWallet(); const { hasAnyBalance, formattedBalance } = useTotalBalance({ address: account?.address, }); @@ -110,18 +110,14 @@ export const ProfileContent = ({ address={account?.address ?? ''} showHeader={false} setCurrentContent={setCurrentContent} - onLogout={() => { - disconnect(); - onLogoutSuccess?.(); - }} /> - + - ) : ( - )} + + } + onClick={() => + setCurrentContent({ + type: 'disconnect-confirm', + props: { + onDisconnect: () => { + disconnect(); + onLogoutSuccess?.(); + }, + onBack: () => + setCurrentContent?.('profile'), + }, + }) + } + aria-label={t('Logout')} + data-testid="logout-button" + /> diff --git a/packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx b/packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx index 07eabf54..08bd48af 100644 --- a/packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx +++ b/packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx @@ -50,8 +50,20 @@ export const SelectWalletContent = ({ }: Props) => { const { t } = useTranslation(); const { isolatedView } = useAccountModalOptions(); - const { account, disconnect } = useWallet(); - const { disconnect: dappKitDisconnect } = useDAppKitWallet(); + const { + account, + accounts: kitAccounts, + setActiveAccount, + connection, + disconnect, + } = useWallet(); + const { + disconnect: dappKitDisconnect, + switchWallet: dappKitSwitchWallet, + requestPermissions: dappKitRequestPermissions, + revokeAccount: dappKitRevokeAccount, + availableMethods: dappKitAvailableMethods, + } = useDAppKitWallet(); const { open: openDappKitModal } = useDAppKitWalletModal(); const { getStoredWallets, setActiveWallet, removeWallet } = useSwitchWallet(); @@ -60,17 +72,65 @@ export const SelectWalletContent = ({ const textSecondary = useToken('colors', 'vechain-kit-text-secondary'); - const [wallets, setWallets] = useState(getStoredWallets()); - const walletsHashRef = useRef(hashWallets(getStoredWallets())); + // On desktop dapp-kit, use `kitAccounts` as the source of truth; + // otherwise fall back to legacy storage. Use a stable primitive key in + // dep arrays — `kitAccounts` reference changes on every valtio write. + const kitAccountsRef = useRef(kitAccounts); + kitAccountsRef.current = kitAccounts; + const kitAccountsKey = useMemo( + () => + kitAccounts + .map((a) => a.address.toLowerCase()) + .sort() + .join('|'), + [kitAccounts], + ); + + const useDappKitAccountsAsSource = useMemo( + () => + connection.isConnectedWithDappKit && + !connection.isInAppBrowser && + kitAccounts.length > 0, + [ + connection.isConnectedWithDappKit, + connection.isInAppBrowser, + kitAccounts.length, + ], + ); + + const initialWallets = useMemo(() => { + if (useDappKitAccountsAsSource) { + const activeLower = account?.address?.toLowerCase(); + return kitAccountsRef.current.map((a) => ({ + address: a.address, + connectedAt: Date.now(), + isActive: a.address.toLowerCase() === activeLower, + })); + } + return getStoredWallets(); + }, [useDappKitAccountsAsSource, kitAccountsKey, account?.address, getStoredWallets]); + + const [wallets, setWallets] = useState(initialWallets); + const walletsHashRef = useRef(hashWallets(initialWallets)); // Function to refresh wallets list const refreshWallets = useCallback(() => { + if (useDappKitAccountsAsSource) { + const activeLower = account?.address?.toLowerCase(); + const next: StoredWallet[] = kitAccountsRef.current.map((a) => ({ + address: a.address, + connectedAt: Date.now(), + isActive: a.address.toLowerCase() === activeLower, + })); + setWallets(next); + walletsHashRef.current = hashWallets(next); + return; + } const updatedWallets = getStoredWallets(); setWallets(updatedWallets); walletsHashRef.current = hashWallets(updatedWallets); - }, [getStoredWallets]); + }, [useDappKitAccountsAsSource, kitAccountsKey, account?.address, getStoredWallets]); - // Refresh wallets list when account changes (new wallet connected) or when wallets are updated useEffect(() => { refreshWallets(); }, [refreshWallets, account?.address]); @@ -143,14 +203,16 @@ export const SelectWalletContent = ({ return; } - // Ensure the wallet that was previously active is saved - // Metadata will be fetched dynamically when needed - if (activeWallet) { - saveWallet(activeWallet.address); + if (useDappKitAccountsAsSource) { + // Dapp-kit v2: switch without re-signing. + setActiveAccount(address); + } else { + if (activeWallet) { + saveWallet(activeWallet.address); + } + setActiveWallet(address); } - setActiveWallet(address); - // Refresh wallets list immediately after switch setTimeout(() => { refreshWallets(); @@ -172,12 +234,14 @@ export const SelectWalletContent = ({ [ activeWalletAddress, activeWallet, - account, + useDappKitAccountsAsSource, + setActiveAccount, setActiveWallet, refresh, setCurrentContent, refreshWallets, saveWallet, + returnTo, ], ); @@ -189,6 +253,13 @@ export const SelectWalletContent = ({ const remainingWallets = wallets.filter( (w) => w.address.toLowerCase() !== wallet.address.toLowerCase(), ); + const supportsRevokeAccount = + useDappKitAccountsAsSource && + Array.isArray(dappKitAvailableMethods) && + dappKitAvailableMethods.includes( + 'wallet_revokeAccountPermission', + ) && + typeof dappKitRevokeAccount === 'function'; // Navigate to remove wallet confirmation screen setCurrentContent({ @@ -197,6 +268,29 @@ export const SelectWalletContent = ({ walletAddress: wallet.address, walletDomain: null, // Domain will be fetched dynamically in RemoveWalletConfirmContent onConfirm: async () => { + if (supportsRevokeAccount) { + await dappKitRevokeAccount(wallet.address); + setTimeout(() => { + refreshWallets(); + }, 50); + + if (remainingWallets.length === 0) { + _onLogoutSuccess?.(); + return; + } + + setCurrentContent({ + type: 'select-wallet', + props: { + setCurrentContent, + onClose: () => {}, + returnTo, + onLogoutSuccess: _onLogoutSuccess, + }, + }); + return; + } + // If removing the active wallet and there are other wallets, switch to the first one if (isActiveWallet && remainingWallets.length > 0) { const nextActiveWallet = remainingWallets[0]; @@ -263,12 +357,49 @@ export const SelectWalletContent = ({ wallets, setActiveWallet, dappKitDisconnect, + dappKitAvailableMethods, + dappKitRevokeAccount, + useDappKitAccountsAsSource, ], ); + const supportsRequestPermissions = + Array.isArray(dappKitAvailableMethods) && + dappKitAvailableMethods.includes('wallet_requestPermissions') && + typeof dappKitRequestPermissions === 'function'; + const supportsRevokeAccount = + Array.isArray(dappKitAvailableMethods) && + dappKitAvailableMethods.includes('wallet_revokeAccountPermission') && + typeof dappKitRevokeAccount === 'function'; + const handleAddNewWallet = useCallback(() => { + if (useDappKitAccountsAsSource) { + // VeWorld v2: prefer EIP-2255 `wallet_requestPermissions`, + // fall back to legacy `thor_switchWallet`. + if (supportsRequestPermissions) { + dappKitRequestPermissions() + .then(() => { + refresh(); + }) + .catch((e) => { + console.error('dapp-kit requestPermissions failed', e); + }); + return; + } + dappKitSwitchWallet().catch((e) => { + console.error('dapp-kit switchWallet failed', e); + }); + return; + } openDappKitModal(); - }, [openDappKitModal]); + }, [ + useDappKitAccountsAsSource, + supportsRequestPermissions, + dappKitRequestPermissions, + dappKitSwitchWallet, + openDappKitModal, + refresh, + ]); const handleLogout = () => { disconnect(); @@ -303,7 +434,10 @@ export const SelectWalletContent = ({ onRemove={() => handleRemoveWallet(activeWallet) } - showRemove={wallets.length > 1} + showRemove={ + !useDappKitAccountsAsSource && + wallets.length > 1 + } /> )} @@ -322,7 +456,10 @@ export const SelectWalletContent = ({ handleWalletSelect(wallet.address) } onRemove={() => handleRemoveWallet(wallet)} - showRemove={true} + showRemove={ + !useDappKitAccountsAsSource || + supportsRevokeAccount + } /> ))} @@ -337,7 +474,9 @@ export const SelectWalletContent = ({ variant="vechainKitSecondary" onClick={handleAddNewWallet} > - {t('Add New Wallet')} + {useDappKitAccountsAsSource + ? t('Change connected accounts') + : t('Add New Wallet')} - )} - - } - onClick={() => - setCurrentContent({ - type: 'disconnect-confirm', - props: { - onDisconnect: () => { - disconnect(); - onLogoutSuccess?.(); + ) : ( + + )}