diff --git a/examples/sample-next-app/package.json b/examples/sample-next-app/package.json index 9a6baecf..ae2ea83f 100644 --- a/examples/sample-next-app/package.json +++ b/examples/sample-next-app/package.json @@ -14,7 +14,7 @@ "@vechain/dapp-kit": "*", "@vechain/dapp-kit-react": "*", "@vechain/dapp-kit-ui": "*", - "next": "14.2.10", + "next": "14.2.15", "react": "^18", "react-dom": "^18" }, diff --git a/examples/sample-next-privy-app/src/app/pages/homepage.tsx b/examples/sample-next-privy-app/src/app/pages/homepage.tsx index 9f233dd3..fcd52275 100644 --- a/examples/sample-next-privy-app/src/app/pages/homepage.tsx +++ b/examples/sample-next-privy-app/src/app/pages/homepage.tsx @@ -30,17 +30,11 @@ const HomePage = (): ReactElement => { const { toggleColorMode: toggleDAppKitPrivyColorMode } = useDAppKitPrivyColorMode(); - const { - isConnected, - connectedAccount, - smartAccount, - isLoadingConnection, - connectionType, - } = useWallet(); + const { connection, smartAccount, connectedWallet } = useWallet(); // A dummy tx sending 0 b3tr tokens const clauses = useMemo(() => { - if (!connectedAccount) return []; + if (!connectedWallet.address) return []; const clausesArray: any[] = []; const abi = new Interface(b3trAbi); @@ -48,14 +42,14 @@ const HomePage = (): ReactElement => { to: b3trMainnetAddress, value: '0x0', data: abi.encodeFunctionData('transfer', [ - connectedAccount, + connectedWallet.address, '0', // 1 B3TR (in wei) ]), comment: `Transfer ${1} B3TR to `, abi: abi.getFunction('transfer'), }); return clausesArray; - }, [connectedAccount]); + }, [connectedWallet.address]); const { sendTransaction, @@ -65,7 +59,7 @@ const HomePage = (): ReactElement => { isTransactionPending, error, } = useSendTransaction({ - signerAccount: smartAccount.address, + signerAccount: smartAccount, privyUIOptions: { title: 'Sign to confirm', description: @@ -74,16 +68,19 @@ const HomePage = (): ReactElement => { }, }); - const transactionModal = useDisclosure(); - const transactionAlert = useDisclosure(); + const transactionToast = useDisclosure(); + const handleTransactionWithToast = useCallback(async () => { + transactionToast.onOpen(); + await sendTransaction(clauses); + }, [sendTransaction, clauses]); - const handleTransaction = useCallback(async () => { - // transactionModal.onOpen(); - transactionAlert.onOpen(); + const transactionModal = useDisclosure(); + const handleTransactionWithModal = useCallback(async () => { + transactionModal.onOpen(); await sendTransaction(clauses); }, [sendTransaction, clauses]); - if (isLoadingConnection) { + if (connection.isLoadingPrivyConnection) { return ( @@ -93,7 +90,7 @@ const HomePage = (): ReactElement => { ); } - if (!isConnected) { + if (!connection.isConnected) { return ( @@ -134,6 +131,7 @@ const HomePage = (): ReactElement => { Deployed: {smartAccount.isDeployed.toString()} + Owner: {smartAccount.owner} )} @@ -141,8 +139,8 @@ const HomePage = (): ReactElement => { Wallet - Address: {connectedAccount} - {Connection Type: {connectionType}} + Address: {connectedWallet?.address} + Connection Type: {connection.source.type} @@ -150,21 +148,30 @@ const HomePage = (): ReactElement => { Actions - + + + + void; }; -export type AccountModalContentTypes = 'main' | 'settings' | 'smart-account'; +export type AccountModalContentTypes = + | 'main' + | 'settings' + | 'smart-account' + | 'accounts'; export const AccountModal = ({ isOpen, onClose }: Props) => { const [isDesktop] = useMediaQuery('(min-width: 768px)'); @@ -34,9 +39,7 @@ export const AccountModal = ({ isOpen, onClose }: Props) => { overflowX: 'hidden', }; - const { connectedAccount } = useWallet(); - - const walletImage = getPicassoImage(connectedAccount ?? ''); + const { selectedAccount } = useWallet(); const [currentContent, setCurrentContent] = useState('main'); @@ -50,10 +53,10 @@ export const AccountModal = ({ isOpen, onClose }: Props) => { switch (currentContent) { case 'main': return ( - ); case 'settings': @@ -68,6 +71,14 @@ export const AccountModal = ({ isOpen, onClose }: Props) => { setCurrentContent={setCurrentContent} /> ); + case 'accounts': + return ( + + ); } }; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountDetailsButton.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/AccountDetailsButton.tsx similarity index 98% rename from packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountDetailsButton.tsx rename to packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/AccountDetailsButton.tsx index 4002a8d8..d505636f 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountDetailsButton.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/AccountDetailsButton.tsx @@ -10,7 +10,7 @@ import { Tag, } from '@chakra-ui/react'; import { ElementType } from 'react'; -import { humanAddress } from '../../utils'; +import { humanAddress } from '../../../utils'; interface AccountDetailsButtonProps { title: string; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/ActionButton.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/ActionButton.tsx similarity index 94% rename from packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/ActionButton.tsx rename to packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/ActionButton.tsx index cc4366a8..46e9fa92 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/ActionButton.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/ActionButton.tsx @@ -19,7 +19,7 @@ interface ActionButtonProps { leftImage?: string; backgroundColor?: string; border?: string; - isDisabled?: boolean; + hide?: boolean; showComingSoon?: boolean; } @@ -30,7 +30,7 @@ export const ActionButton = ({ description, onClick, leftImage, - isDisabled = false, + hide = false, showComingSoon = false, }: ActionButtonProps) => { return ( @@ -40,8 +40,8 @@ export const ActionButton = ({ h={'fit-content'} py={4} onClick={onClick} - opacity={isDisabled ? 0.5 : 1} - isDisabled={isDisabled} + display={hide ? 'none' : 'flex'} + isDisabled={showComingSoon} > diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/index.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/index.ts new file mode 100644 index 00000000..ca7412b2 --- /dev/null +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Components/index.ts @@ -0,0 +1,2 @@ +export * from './AccountDetailsButton'; +export * from './ActionButton'; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountModalMainContent.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/AccountsContent.tsx similarity index 59% rename from packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountModalMainContent.tsx rename to packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/AccountsContent.tsx index 35ce87fe..8dbb128d 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/AccountModalMainContent.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/AccountsContent.tsx @@ -2,23 +2,20 @@ import { Button, Grid, HStack, - Image, ModalBody, ModalCloseButton, ModalFooter, ModalHeader, - Text, VStack, useColorMode, } from '@chakra-ui/react'; -import { useWallet } from '../../hooks'; +import { useWallet, Wallet } from '../../../hooks'; import { RxExit } from 'react-icons/rx'; -import { AddressDisplay } from '../common/AddressDisplay'; -import { FadeInViewFromBottom } from '../common'; -import { AccountDetailsButton } from './AccountDetailsButton'; -import packageJson from '../../../../package.json'; +import { AddressDisplay } from '../../common/AddressDisplay'; +import { FadeInViewFromBottom, ModalBackButton } from '../../common'; +import { AccountDetailsButton } from '../Components/AccountDetailsButton'; import { MdAccountCircle, MdOutlineNavigateNext } from 'react-icons/md'; -import { AccountModalContentTypes } from './AccountModal'; +import { AccountModalContentTypes } from '../AccountModal'; import { HiOutlineWallet } from 'react-icons/hi2'; type Props = { @@ -26,32 +23,15 @@ type Props = { React.SetStateAction >; onClose: () => void; - walletImage: string; + wallet?: Wallet; }; -export const AccountModalMainContent = ({ - setCurrentContent, - onClose, - walletImage, -}: Props) => { +export const AccountsContent = ({ setCurrentContent, onClose }: Props) => { const { colorMode } = useColorMode(); const isDark = colorMode === 'dark'; - const { - logoutAndDisconnect, - isConnectedWithPrivy, - connectedAccount, - smartAccount, - vetDomain, - connectionType, - } = useWallet(); - - const connectionSource = - connectionType === 'privy-cross-app' - ? 'app' - : connectionType === 'privy' - ? 'social' - : 'wallet'; + const { disconnect, connection, selectedAccount, connectedWallet } = + useWallet(); return ( @@ -61,31 +41,15 @@ export const AccountModalMainContent = ({ textAlign={'center'} color={isDark ? '#dfdfdd' : '#4d4d4d'} > - {'Connected to ' + connectionSource} - - v{packageJson.version} - + {'Your accounts'} - - - + setCurrentContent('main')} /> + - {isConnectedWithPrivy ? ( + {connection.isConnectedWithPrivy ? ( { setCurrentContent('smart-account'); @@ -104,7 +68,7 @@ export const AccountModalMainContent = ({ /> { setCurrentContent('settings'); }} @@ -113,10 +77,7 @@ export const AccountModalMainContent = ({ /> ) : ( - + )} @@ -125,7 +86,7 @@ export const AccountModalMainContent = ({ + + v{packageJson.version} + + + + + ); +}; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/SmartAccountContent.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/SmartAccountContent.tsx similarity index 70% rename from packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/SmartAccountContent.tsx rename to packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/SmartAccountContent.tsx index 8fa8ef0e..4ea6d1fe 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/SmartAccountContent.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/SmartAccountContent.tsx @@ -7,21 +7,16 @@ import { ModalHeader, Text, useColorMode, - Divider, Link, Icon, } from '@chakra-ui/react'; -import { useWallet } from '../../hooks'; +import { useWallet } from '../../../hooks'; import React, { useState } from 'react'; -import { AddressDisplay } from '../common/AddressDisplay'; -import { MdOutlineNavigateNext } from 'react-icons/md'; +import { AddressDisplay } from '../../common/AddressDisplay'; import { IoOpenOutline } from 'react-icons/io5'; -import { ActionButton } from './ActionButton'; -import { FadeInViewFromBottom, ModalBackButton } from '../common'; -import { FadeInViewFromRight } from '../common'; -import { AccountModalContentTypes } from './AccountModal'; -import { FaRegAddressCard } from 'react-icons/fa'; -import { getPicassoImage } from '../../utils'; +import { FadeInViewFromBottom, ModalBackButton } from '../../common'; +import { AccountModalContentTypes } from '../AccountModal'; +import { getPicassoImage } from '../../../utils'; type Props = { setCurrentContent: React.Dispatch< @@ -40,7 +35,7 @@ export const SmartAccountContent = ({ setCurrentContent }: Props) => { const [isExpanded, setIsExpanded] = useState(false); return ( - + { {'Smart Account'} - setCurrentContent('main')} /> + setCurrentContent('accounts')} /> - + - To allow you a smooth omboarding on VeChain we are + To allow you a smooth onboarding on VeChain we are helping you manage a wallet. @@ -104,24 +99,8 @@ export const SmartAccountContent = ({ setCurrentContent }: Props) => { {isExpanded ? 'Read less' : 'Read more'} - - - - - { - // linkPasskey(); - }} - isDisabled={true} - showComingSoon={true} - leftIcon={FaRegAddressCard} - rightIcon={MdOutlineNavigateNext} - /> - - + ); }; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/WalletSettingsContent.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/WalletSettingsContent.tsx similarity index 68% rename from packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/WalletSettingsContent.tsx rename to packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/WalletSettingsContent.tsx index a8a071a6..697d29c3 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/WalletSettingsContent.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/WalletSettingsContent.tsx @@ -7,18 +7,17 @@ import { ModalHeader, useColorMode, } from '@chakra-ui/react'; -import { usePrivy, useWallet } from '../../hooks'; -import { AddressDisplay } from '../common/AddressDisplay'; +import { usePrivy, useWallet, Wallet } from '../../../hooks'; +import { AddressDisplay } from '../../common/AddressDisplay'; import { GiHouseKeys } from 'react-icons/gi'; import { MdOutlineNavigateNext } from 'react-icons/md'; import { IoIosFingerPrint } from 'react-icons/io'; -import { ActionButton } from './ActionButton'; -import { ModalBackButton } from '../common'; -import { useDAppKitPrivyConfig } from '../../DAppKitPrivyProvider'; -import { FadeInViewFromRight } from '../common'; -import { AccountModalContentTypes } from './AccountModal'; +import { ActionButton } from '../Components/ActionButton'; +import { ModalBackButton } from '../../common'; +import { useDAppKitPrivyConfig } from '../../../DAppKitPrivyProvider'; +import { FadeInViewFromBottom } from '../../common'; +import { AccountModalContentTypes } from '../AccountModal'; import { FaRegAddressCard } from 'react-icons/fa'; -import { getPicassoImage } from '../../utils'; type Props = { setCurrentContent: (content: AccountModalContentTypes) => void; @@ -28,20 +27,18 @@ export const WalletSettingsContent = ({ setCurrentContent }: Props) => { const { exportWallet, linkPasskey } = usePrivy(); const { privyConfig } = useDAppKitPrivyConfig(); - const { privyEmbeddedWallet, isCrossAppPrivyAccount, crossAppAccount } = - useWallet(); + const { embeddedWallet, connection, crossAppWallet } = useWallet(); - const account = isCrossAppPrivyAccount - ? crossAppAccount - : privyEmbeddedWallet; - - const walletImage = getPicassoImage(account ?? ''); + // Privy always creates an embedded wallet, so if the user is connected with app we use the other + const account: Wallet = connection.isConnectedWithCrossAppPrivy + ? crossAppWallet + : embeddedWallet; const { colorMode } = useColorMode(); const isDark = colorMode === 'dark'; return ( - + { {'Wallet Settings'} - setCurrentContent('main')} /> + setCurrentContent('accounts')} /> - - + + @@ -66,7 +67,7 @@ export const WalletSettingsContent = ({ setCurrentContent }: Props) => { onClick={() => { exportWallet(); }} - isDisabled={isCrossAppPrivyAccount} + hide={connection.isConnectedWithCrossAppPrivy} leftIcon={GiHouseKeys} rightIcon={MdOutlineNavigateNext} /> @@ -78,7 +79,7 @@ export const WalletSettingsContent = ({ setCurrentContent }: Props) => { onClick={() => { linkPasskey(); }} - isDisabled={isCrossAppPrivyAccount} + hide={connection.isConnectedWithCrossAppPrivy} leftIcon={IoIosFingerPrint} rightIcon={MdOutlineNavigateNext} /> @@ -90,7 +91,6 @@ export const WalletSettingsContent = ({ setCurrentContent }: Props) => { onClick={() => { // linkPasskey(); }} - isDisabled={true} showComingSoon={true} leftIcon={FaRegAddressCard} rightIcon={MdOutlineNavigateNext} @@ -98,6 +98,6 @@ export const WalletSettingsContent = ({ setCurrentContent }: Props) => { - + ); }; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/index.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/index.ts new file mode 100644 index 00000000..f690d430 --- /dev/null +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/AccountModal/Contents/index.ts @@ -0,0 +1,4 @@ +export * from './AccountsContent'; +export * from './WalletSettingsContent'; +export * from './SmartAccountContent'; +export * from './MainContent'; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/ConnectModal/EcosystemContent.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/ConnectModal/EcosystemContent.tsx index b24a61ca..ee8d3deb 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/ConnectModal/EcosystemContent.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/ConnectModal/EcosystemContent.tsx @@ -36,7 +36,7 @@ export const EcosystemContent = ({ const { authenticated } = usePrivy(); const { loginWithCrossAppAccount, linkCrossAppAccount } = useCrossAppAccounts(); - const { isCrossAppPrivyAccount } = useWallet(); + const { connection } = useWallet(); const [crossAppLogin, setCrossAppLogin] = useState(false); const connectWithVebetterDaoApps = async (appId: string) => { @@ -46,12 +46,16 @@ export const EcosystemContent = ({ }; useEffect(() => { - if (!isCrossAppPrivyAccount && crossAppLogin && authenticated) { + if ( + connection.source.type === 'privy-cross-app' && + crossAppLogin && + authenticated + ) { linkCrossAppAccount({ appId: `${privyConfig?.ecosystemAppsID?.[0]}`, }); } - }, [isCrossAppPrivyAccount, crossAppLogin, authenticated]); + }, [connection.source.type, crossAppLogin, authenticated]); return ( diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/WalletButton/WalletButton.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/WalletButton/WalletButton.tsx index d64efe80..e8e06b9a 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/WalletButton/WalletButton.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/WalletButton/WalletButton.tsx @@ -3,12 +3,10 @@ import { useWallet } from '../../hooks'; import { ConnectModal } from '../ConnectModal'; import { AccountModal } from '../AccountModal'; import { useDAppKitPrivyConfig } from '../../DAppKitPrivyProvider'; -import { getPicassoImage, humanAddress } from '../../utils'; +import { humanAddress } from '../../utils'; export const WalletButton = () => { - const { isConnected, isLoadingConnection, selectedAddress } = useWallet(); - - const walletImage = getPicassoImage(selectedAddress ?? ''); + const { connection, selectedAccount } = useWallet(); const connectModal = useDisclosure(); const accountModal = useDisclosure(); @@ -17,42 +15,36 @@ export const WalletButton = () => { return ( <> - {isLoadingConnection ? ( -

Loading...

+ {connection.isConnected ? ( + ) : ( - <> - {isConnected ? ( - - ) : ( - - )} + + )} - + - - - )} + ); }; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AccountSelector.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AccountSelector.tsx new file mode 100644 index 00000000..3b684f2e --- /dev/null +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AccountSelector.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { Text, Icon, HStack, Button, useColorMode } from '@chakra-ui/react'; +import { humanAddress } from '../../utils'; +import { Wallet } from '../../hooks'; +import { IoIosArrowDown } from 'react-icons/io'; +import { useState } from 'react'; +import { IoCheckmarkOutline, IoCopyOutline } from 'react-icons/io5'; + +type Props = { + wallet: Wallet; + size?: string; + onClick?: () => void; +}; + +export const AccountSelector = ({ wallet, size = 'xl', onClick }: Props) => { + const { colorMode } = useColorMode(); + const isDark = colorMode === 'dark'; + + const [copied, setCopied] = useState(false); + + const copyToClipboard = async (textToCopy: string) => { + await navigator.clipboard.writeText(textToCopy); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 2000); + }; + + return ( + + ); +}; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AddressDisplay.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AddressDisplay.tsx index 6b679fed..4dc2e82a 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AddressDisplay.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/AddressDisplay.tsx @@ -4,23 +4,22 @@ import { Text, VStack, Icon, HStack } from '@chakra-ui/react'; import { useState } from 'react'; import { IoCopyOutline, IoCheckmarkOutline } from 'react-icons/io5'; import { humanAddress } from '../../utils'; +import { Wallet } from '../../hooks'; type Props = { - address: string; + wallet: Wallet; label?: string; - domain?: string; size?: string; }; -export const AddressDisplay = ({ - address, - label, - domain, - size = 'lg', -}: Props) => { +export const AddressDisplay = ({ wallet, label, size = 'lg' }: Props) => { const [copied, setCopied] = useState(false); + const [copiedDomain, setCopiedDomain] = useState(false); - const copyToClipboard = async (textToCopy: string) => { + const copyToClipboard = async ( + textToCopy: string, + setCopied: (value: boolean) => void, + ) => { await navigator.clipboard.writeText(textToCopy); setCopied(true); setTimeout(() => { @@ -36,37 +35,59 @@ export const AddressDisplay = ({ {label} )} - {domain ? ( + {wallet.domain ? ( - {domain} + {wallet.domain} + copyToClipboard( + wallet.domain || '', + setCopiedDomain, + ) + } + /> + + + + {'('} + {humanAddress(wallet.address, 8, 7)} + {')'} + + copyToClipboard(address)} + onClick={() => + copyToClipboard(wallet.address, setCopied) + } /> - - {'('} - {humanAddress(address, 8, 7)} - {')'} - ) : ( - {humanAddress(address, 6, 4)} + {humanAddress(wallet.address, 6, 4)} copyToClipboard(address)} + onClick={() => + copyToClipboard(wallet.address, setCopied) + } /> )} diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/index.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/index.ts index cfb8983a..235ef0a2 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/index.ts +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/components/common/index.ts @@ -4,3 +4,4 @@ export * from './FadeInViewFromBottom'; export * from './ModalBackButton'; export * from './AddressDisplay'; export * from './AppLogos'; +export * from './AccountSelector'; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/index.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/index.ts index df89a652..cdb1236b 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/index.ts +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/index.ts @@ -7,3 +7,4 @@ export { usePrivy, useCrossAppAccounts } from '@privy-io/react-auth'; export { useConnex } from '@vechain/dapp-kit-react'; export { useWalletModal } from '@vechain/dapp-kit-react'; export { useColorMode as useDAppKitPrivyColorMode } from '@chakra-ui/react'; +export * from './useCachedVechainDomain'; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useCachedVechainDomain.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useCachedVechainDomain.ts new file mode 100644 index 00000000..c7bb580b --- /dev/null +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useCachedVechainDomain.ts @@ -0,0 +1,67 @@ +import { useVechainDomain } from '@vechain/dapp-kit-react'; + +interface VechainDomainResult { + address?: string; + domain?: string; + isValidAddressOrDomain: boolean; + isLoading: boolean; +} + +interface CachedVechainDomainResult { + domainResult: VechainDomainResult; + getCachedDomain: () => string | null; + saveCachedDomain: (domain: string) => void; +} + +const DOMAIN_CACHE_KEY = 'vechainDomainCache'; +const CACHE_EXPIRY_TIME = 24 * 60 * 60 * 1000; // 24 hours +export const useCachedVechainDomain = ( + address: string, +): CachedVechainDomainResult => { + const domainResult = useVechainDomain({ addressOrDomain: address }); + + // Try to get from cache first + const getCachedDomain = (): string | null => { + const cachedData = localStorage.getItem(DOMAIN_CACHE_KEY); + if (cachedData) { + const cachedDomains = JSON.parse(cachedData); + const cachedDomain = cachedDomains.find( + (domain: { + address: string; + domain: string; + timestamp: number; + }) => domain.address === address, + ); + if (cachedDomain) { + const currentTime = Date.now(); + if (currentTime - cachedDomain.timestamp < CACHE_EXPIRY_TIME) { + return cachedDomain.domain; + } + } + } + return null; + }; + + // Save to cache + const saveCachedDomain = (domain: string) => { + const cachedData = localStorage.getItem(DOMAIN_CACHE_KEY); + const cachedDomains = cachedData ? JSON.parse(cachedData) : []; + const cachedDomain = cachedDomains.find( + (domain: { address: string; domain: string; timestamp: number }) => + domain.address === address, + ); + if (cachedDomain) { + cachedDomain.domain = domain; + cachedDomain.timestamp = Date.now(); + } else { + cachedDomains.push({ address, domain, timestamp: Date.now() }); + } + localStorage.setItem(DOMAIN_CACHE_KEY, JSON.stringify(cachedDomains)); + }; + + return { + domainResult, + getCachedDomain, + saveCachedDomain, + }; +}; diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSendTransaction.ts b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSendTransaction.ts index 15d0adb1..b11eb614 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSendTransaction.ts +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSendTransaction.ts @@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useConnex } from '@vechain/dapp-kit-react'; import { Transaction } from 'thor-devkit'; import { useDAppKitPrivyConfig } from '../DAppKitPrivyProvider'; -import { useWallet } from './useWallet'; +import { useWallet, Wallet } from './useWallet'; import { useSmartAccount } from './useSmartAccount'; import { EnhancedClause, @@ -69,7 +69,7 @@ const estimateTxGasWithNext = async ( * @param suggestedMaxGas the suggested max gas for the transaction */ type UseSendTransactionProps = { - signerAccount?: string | null; + signerAccount?: Wallet | null; clauses?: | EnhancedClause[] | (() => EnhancedClause[]) @@ -129,7 +129,7 @@ export const useSendTransaction = ({ const { dappKitConfig, feeDelegationConfig } = useDAppKitPrivyConfig(); const nodeUrl = dappKitConfig.nodeUrl; - const { isConnectedWithPrivy } = useWallet(); + const { connection } = useWallet(); const smartAccount = useSmartAccount(); /** @@ -154,7 +154,7 @@ export const useSendTransaction = ({ parsedClauses = clauses; } - if (isConnectedWithPrivy) { + if (connection.isConnectedWithPrivy) { return parsedClauses.map((clause) => { return { to: clause.to ?? '', @@ -174,7 +174,7 @@ export const useSendTransaction = ({ */ const sendTransaction = useCallback( async (clauses: EnhancedClause[]) => { - if (isConnectedWithPrivy) { + if (connection.isConnectedWithPrivy) { return await smartAccount.sendTransaction({ txClauses: clauses, ...privyUIOptions, @@ -194,7 +194,7 @@ export const useSendTransaction = ({ try { gasLimitNext = await estimateTxGasWithNext( [...clauses], - signerAccount, + signerAccount.address, undefined, nodeUrl, ); @@ -209,10 +209,10 @@ export const useSendTransaction = ({ // specify gasLimit if we have a suggested or an estimation if (parsedGasLimit > 0) return transaction - .signer(signerAccount) + .signer(signerAccount.address) .gas(parseInt(parsedGasLimit.toString())) .request(); - else return transaction.signer(signerAccount).request(); + else return transaction.signer(signerAccount.address).request(); } return transaction.request(); }, diff --git a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSmartAccount.tsx b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSmartAccount.tsx index bdf6814d..93773b5d 100644 --- a/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSmartAccount.tsx +++ b/packages/dapp-kit-react-privy/src/DAppKitPrivyProvider/hooks/useSmartAccount.tsx @@ -24,7 +24,7 @@ import { export interface SmartAccountContextType { address: string | undefined; - ownerAddress: string | undefined; + owner: string | undefined; embeddedWallet: ConnectedWallet | undefined; isDeployed: boolean; sendTransaction: (tx: { @@ -79,7 +79,7 @@ export const SmartAccountProvider = ({ const [smartAccountAddress, setSmartAccountAddress] = useState< string | undefined >(); - const [ownerAddress, setOwnerAddress] = useState(); + const [owner, setOwner] = useState(); const [chainId, setChainId] = useState(''); const thor = ThorClient.fromUrl(nodeUrl); const [isDeployed, setIsDeployed] = useState(false); @@ -99,7 +99,7 @@ export const SmartAccountProvider = ({ * Set the owner address to the connected account */ useEffect(() => { - setOwnerAddress(connectedAccount); + setOwner(connectedAccount); }, [connectedAccount]); /** @@ -381,7 +381,7 @@ export const SmartAccountProvider = ({ Promise; - vetDomain: string | undefined; - privyUser: any; + + // Disconnect function + disconnect: () => Promise; }; export const useWallet = (): UseWalletReturnType => { const { user, authenticated, logout, ready } = usePrivy(); - const { account: dappKitAccount, disconnect: dappKitDisconnect } = useDappKitWallet(); - const smartAccount = useSmartAccount(); + // Connection states const isConnectedWithDappKit = !!dappKitAccount; const isConnectedWithPrivy = authenticated && !!user; const isConnected = isConnectedWithDappKit || isConnectedWithPrivy; - const isCrossAppPrivyAccount = Boolean( user?.linkedAccounts?.some((account) => account.type === 'cross_app'), ); - const connectionType = isCrossAppPrivyAccount - ? 'privy-cross-app' + // Connection type + const connectionSource: ConnectionSource = isCrossAppPrivyAccount + ? { + type: 'privy-cross-app', + displayName: 'App', + } : isConnectedWithDappKit - ? 'wallet' - : 'privy'; - + ? { + type: 'wallet', + displayName: 'Wallet', + } + : { + type: 'privy', + displayName: 'Social', + }; + + // Get cross app account const crossAppAccount = user?.linkedAccounts.find( (account) => account.type === 'cross_app', ); + // Get embedded wallet const privyEmbeddedWallet = user?.wallet?.address; - // The connectedAccount can be either: - // 1. The dappKit connected account - // 2. The owner of the smart account (either crossAppAccount or privyEmbeddedWallet) - const connectedAccount = isConnectedWithDappKit + // Get connected and selected accounts + const connectedWalletAddress = isConnectedWithDappKit ? dappKitAccount - : smartAccount.ownerAddress; + : smartAccount.owner; const selectedAddress = isConnectedWithDappKit ? dappKitAccount : smartAccount.address; - const selectedAddressVetDomain = useVechainDomain({ - addressOrDomain: selectedAddress, - }); + // Use cached domain lookups for each address + const walletDomain = useCachedVechainDomain(dappKitAccount ?? '') + .domainResult.domain; + const smartAccountDomain = useCachedVechainDomain( + smartAccount.address ?? '', + ).domainResult.domain; + const embeddedWalletDomain = useCachedVechainDomain( + privyEmbeddedWallet ?? '', + ).domainResult.domain; + const crossAppAccountDomain = useCachedVechainDomain( + crossAppAccount?.embeddedWallets?.[0]?.address ?? '', + ).domainResult.domain; + + const selectedAccount = { + address: selectedAddress ?? '', + domain: useCachedVechainDomain(selectedAddress ?? '').domainResult + .domain, + image: getPicassoImage(selectedAddress ?? ''), + }; - const logoutAndDisconnect = async () => { + const connectedWallet = { + address: connectedWalletAddress ?? '', + domain: useCachedVechainDomain(connectedWalletAddress ?? '') + .domainResult.domain, + image: getPicassoImage(connectedWalletAddress ?? ''), + }; + + // Disconnect function + const disconnect = async () => { if (isConnectedWithDappKit) { dappKitDisconnect(); } else { @@ -80,25 +140,40 @@ export const useWallet = (): UseWalletReturnType => { } }; - const isLoadingConnection = !ready; - return { - isConnected, - isConnectedWithPrivy, - isConnectedWithDappKit, - isLoadingConnection, - isCrossAppPrivyAccount, - connectionType, - - connectedAccount, - selectedAddress, - dappKitAccount, - crossAppAccount: crossAppAccount?.embeddedWallets?.[0]?.address, - privyEmbeddedWallet, - smartAccount, - - logoutAndDisconnect, - vetDomain: selectedAddressVetDomain.domain, + wallet: { + address: dappKitAccount ?? '', + domain: walletDomain ?? '', + image: getPicassoImage(selectedAccount.address), + }, + smartAccount: { + address: smartAccount.address ?? '', + domain: smartAccountDomain ?? '', + image: getPicassoImage(selectedAccount.address), + isDeployed: smartAccount.isDeployed, + owner: smartAccount.owner ?? '', + }, + embeddedWallet: { + address: privyEmbeddedWallet ?? '', + domain: embeddedWalletDomain ?? '', + image: getPicassoImage(selectedAccount.address), + }, + crossAppWallet: { + address: crossAppAccount?.embeddedWallets?.[0]?.address ?? '', + domain: crossAppAccountDomain ?? '', + image: getPicassoImage(selectedAccount.address), + }, + selectedAccount, + connectedWallet, privyUser: user, + connection: { + isConnected, + isConnectedWithPrivy, + isConnectedWithDappKit, + isConnectedWithCrossAppPrivy: isCrossAppPrivyAccount, + isLoadingPrivyConnection: !ready, + source: connectionSource, + }, + disconnect, }; }; diff --git a/packages/dapp-kit-react/package.json b/packages/dapp-kit-react/package.json index 1ef054a0..4a2c5ae0 100644 --- a/packages/dapp-kit-react/package.json +++ b/packages/dapp-kit-react/package.json @@ -1,6 +1,6 @@ { "name": "@vechain/dapp-kit-react", - "version": "1.2.2", + "version": "1.3.0", "private": false, "homepage": "https://github.com/vechain/vechain-dapp-kit", "repository": "github:vechain/vechain-dapp-kit", diff --git a/packages/dapp-kit-ui/package.json b/packages/dapp-kit-ui/package.json index c7ccd485..74b9a06e 100644 --- a/packages/dapp-kit-ui/package.json +++ b/packages/dapp-kit-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vechain/dapp-kit-ui", - "version": "1.2.2", + "version": "1.3.0", "private": false, "description": "Vanilla JS DAppKit", "keywords": [ diff --git a/packages/dapp-kit/package.json b/packages/dapp-kit/package.json index ce8ff807..d5001ebd 100644 --- a/packages/dapp-kit/package.json +++ b/packages/dapp-kit/package.json @@ -1,6 +1,6 @@ { "name": "@vechain/dapp-kit", - "version": "1.2.2", + "version": "1.3.0", "private": false, "homepage": "https://github.com/vechain/vechain-dapp-kit", "repository": "github:vechain/vechain-dapp-kit", diff --git a/yarn.lock b/yarn.lock index b8224c70..dafb6855 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16591,9 +16591,9 @@ nan@^2.18.0, nan@^2.19.0: integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== nanoid@^3.3.6, nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== napi-wasm@^1.1.0: version "1.1.0"