diff --git a/README.md b/README.md index a12e23844..f1ad5654e 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ interface CreateConfigOptions { projectId: string } + guest?: boolean + google?: | false | { @@ -96,6 +98,8 @@ const config = createConfig('waas', { appName: 'Demo Dapp', waasConfigKey: '', + guest: true, + google: { clientId: '' }, @@ -107,7 +111,9 @@ const config = createConfig('waas', { walletConnect: { projectId: '' - } + }, + + email: true }) function App() { @@ -161,6 +167,8 @@ const connectors = getDefaultConnectors('universal', { waasConfigKey: '', + guest: true + google: { clientId }, @@ -173,6 +181,8 @@ const connectors = getDefaultConnectors('universal', { walletConnect: { projectId: '' } + + email: true }) */ diff --git a/examples/components/src/Footer.tsx b/examples/components/src/Footer.tsx index 0816654b3..8a1ca980d 100644 --- a/examples/components/src/Footer.tsx +++ b/examples/components/src/Footer.tsx @@ -1,4 +1,4 @@ -import { Button, Image, Text, useTheme } from '@0xsequence/design-system' +import { Button, Image, Text } from '@0xsequence/design-system' interface BottomPageLink { label: string diff --git a/packages/connect/README.md b/packages/connect/README.md index 4c44fd538..73cd832cf 100644 --- a/packages/connect/README.md +++ b/packages/connect/README.md @@ -64,6 +64,8 @@ interface CreateConfigOptions { projectId: string } + guest?: boolean + google?: | boolean | { @@ -97,6 +99,8 @@ const config = createConfig('waas', { appName: 'Demo Dapp', waasConfigKey: '', + guest: true, + google: { clientId: '' }, @@ -108,7 +112,9 @@ const config = createConfig('waas', { walletConnect: { projectId: '' - } + }, + + email: true }) function App() { diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 7b791c786..fcb5d64b9 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -44,7 +44,15 @@ export { // Utils export { getConnectWallets } from './utils/getConnectWallets.js' -export { capitalize, compareAddress, formatAddress, formatDisplay, isEmailValid, truncateAtMiddle } from './utils/helpers.js' +export { + capitalize, + compareAddress, + formatAddress, + formatDisplay, + isEmailValid, + truncateAtIndex, + truncateAtMiddle +} from './utils/helpers.js' export { createNativeTokenBalance, getNativeTokenInfoByChainId } from './utils/tokens.js' export { getModalPositionCss } from './utils/styling.js' export { getNetwork, getNetworkBackgroundColor, getNetworkColor } from './utils/networks.js' diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 6a02b1674..f724a3d3f 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -8,7 +8,6 @@ export const styles = String.raw` "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --color-red-500: oklch(63.7% 0.237 25.331); --color-violet-600: oklch(54.1% 0.281 293.009); - --color-gray-500: oklch(55.1% 0.027 264.364); --color-black: #000; --color-white: #fff; --spacing: 0.25rem; @@ -212,6 +211,9 @@ export const styles = String.raw` .relative { position: relative; } + .sticky { + position: sticky; + } .inset-0 { inset: calc(var(--spacing) * 0); } @@ -290,9 +292,6 @@ export const styles = String.raw` .-m-\[1px\] { margin: calc(1px * -1); } - .m-0 { - margin: calc(var(--spacing) * 0); - } .m-4 { margin: calc(var(--spacing) * 4); } @@ -449,6 +448,9 @@ export const styles = String.raw` .h-\[1px\] { height: 1px; } + .h-\[2px\] { + height: 2px; + } .h-\[17px\] { height: 17px; } @@ -766,6 +768,11 @@ export const styles = String.raw` .justify-self-center { justify-self: center; } + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } .overflow-hidden { overflow: hidden; } @@ -793,6 +800,9 @@ export const styles = String.raw` .rounded { border-radius: 0.25rem; } + .rounded-2xl { + border-radius: var(--radius-2xl); + } .rounded-full { border-radius: calc(infinity * 1px); } @@ -818,22 +828,10 @@ export const styles = String.raw` border-top-left-radius: var(--radius-2xl); border-top-right-radius: var(--radius-2xl); } - .rounded-t-none { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - .rounded-t-xl { - border-top-left-radius: var(--radius-xl); - border-top-right-radius: var(--radius-xl); - } .rounded-b-none { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } - .rounded-b-xl { - border-bottom-right-radius: var(--radius-xl); - border-bottom-left-radius: var(--radius-xl); - } .border { border-style: var(--tw-border-style); border-width: 1px; @@ -975,6 +973,9 @@ export const styles = String.raw` .fill-background-raised { fill: var(--seq-color-background-raised); } + .object-contain { + object-fit: contain; + } .object-cover { object-fit: cover; } @@ -1065,9 +1066,6 @@ export const styles = String.raw` .pt-5 { padding-top: calc(var(--spacing) * 5); } - .pt-6 { - padding-top: calc(var(--spacing) * 6); - } .pt-\[60px\] { padding-top: 60px; } @@ -1252,9 +1250,6 @@ export const styles = String.raw` .text-black { color: var(--color-black); } - .text-gray-500 { - color: var(--color-gray-500); - } .text-info { color: var(--seq-color-info); } @@ -1317,6 +1312,9 @@ export const styles = String.raw` .opacity-50 { opacity: 50%; } + .opacity-75 { + opacity: 75%; + } .opacity-100 { opacity: 100%; } @@ -1328,6 +1326,10 @@ export const styles = String.raw` --tw-shadow: 0 0 10px 0 var(--tw-shadow-color, rgba(0,0,0,0.5)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .ring-1 { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); diff --git a/packages/connect/src/utils/helpers.ts b/packages/connect/src/utils/helpers.ts index 15bd9d734..c260fd964 100644 --- a/packages/connect/src/utils/helpers.ts +++ b/packages/connect/src/utils/helpers.ts @@ -107,6 +107,16 @@ export const truncateAtMiddle = (text: string, truncateAt: number) => { return finalText } +export const truncateAtIndex = (text: string, truncateIndex: number) => { + let finalText = text + + if (text.length >= truncateIndex) { + finalText = text.slice(0, truncateIndex) + '...' + text.slice(text.length - 4, text.length) + } + + return finalText +} + export const formatAddress = (text: string) => { return `0x${truncateAtMiddle(text?.substring(2) || '', 8)}` } diff --git a/packages/hooks/src/hooks/IndexerGateway/useGetSingleTokenBalance.ts b/packages/hooks/src/hooks/IndexerGateway/useGetSingleTokenBalance.ts index 58ef9f942..c8db20cc3 100644 --- a/packages/hooks/src/hooks/IndexerGateway/useGetSingleTokenBalance.ts +++ b/packages/hooks/src/hooks/IndexerGateway/useGetSingleTokenBalance.ts @@ -1,4 +1,4 @@ -import type { SequenceIndexerGateway } from '@0xsequence/indexer' +import { ContractVerificationStatus, type SequenceIndexerGateway } from '@0xsequence/indexer' import { useQuery } from '@tanstack/react-query' import { QUERY_KEYS, time, ZERO_ADDRESS } from '../../constants.js' @@ -12,6 +12,7 @@ export interface GetSingleTokenBalanceArgs { accountAddress: string contractAddress: string tokenId?: string + hideUnlistedTokens?: boolean } const getSingleTokenBalance = async (args: GetSingleTokenBalanceArgs, indexerGatewayClient: SequenceIndexerGateway) => { @@ -20,7 +21,8 @@ const getSingleTokenBalance = async (args: GetSingleTokenBalanceArgs, indexerGat filter: { accountAddresses: [args.accountAddress], contractWhitelist: [args.contractAddress], - omitNativeBalances: false + omitNativeBalances: false, + contractStatus: args.hideUnlistedTokens ? ContractVerificationStatus.VERIFIED : ContractVerificationStatus.ALL } }) diff --git a/packages/wallet-widget/src/components/CollectibleTileImage.tsx b/packages/wallet-widget/src/components/CollectibleTileImage.tsx deleted file mode 100644 index 34540e0b7..000000000 --- a/packages/wallet-widget/src/components/CollectibleTileImage.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Card, Image } from '@0xsequence/design-system' -import React from 'react' - -interface CollectibleTileImageProps { - imageUrl?: string -} - -export const CollectibleTileImage = ({ imageUrl }: CollectibleTileImageProps) => { - return ( - - - - ) -} diff --git a/packages/wallet-widget/src/components/ConnectorLogos/getConnectorLogos.tsx b/packages/wallet-widget/src/components/ConnectorLogos/getConnectorLogos.tsx index 7e2f5ae71..dc596da80 100644 --- a/packages/wallet-widget/src/components/ConnectorLogos/getConnectorLogos.tsx +++ b/packages/wallet-widget/src/components/ConnectorLogos/getConnectorLogos.tsx @@ -40,6 +40,6 @@ export const getConnectorLogo = (connectorId: string, isDarkMode = false): React case 'wallet-connect': return default: - return <> + return null } } diff --git a/packages/wallet-widget/src/components/CopyButton.tsx b/packages/wallet-widget/src/components/CopyButton.tsx index 3775ffecd..954e849ab 100644 --- a/packages/wallet-widget/src/components/CopyButton.tsx +++ b/packages/wallet-widget/src/components/CopyButton.tsx @@ -1,4 +1,4 @@ -import { Button, CheckmarkIcon, CopyIcon } from '@0xsequence/design-system' +import { Button, CheckmarkIcon, CopyIcon, Text } from '@0xsequence/design-system' import { useClipboard } from '@0xsequence/hooks' import type { ComponentProps } from 'react' @@ -6,23 +6,32 @@ type ButtonProps = ComponentProps interface CopyButtonProps extends ButtonProps { text: string - buttonVariant: 'icon' | 'with-label' + includeLabel?: boolean + size?: 'xs' | 'sm' | 'md' | 'lg' } export const CopyButton = (props: CopyButtonProps) => { - const { buttonVariant = 'icon', text, size = 'xs', ...rest } = props + const { includeLabel = false, text, size = 'xs' } = props const [isCopied, setCopied] = useClipboard({ timeout: 4000 }) const label = isCopied ? 'Copied!' : 'Copy' return ( - - )} -
-
-
-
-
- - {label} - -
-
- {onBackPress &&
} -
+
{header}
{children}
- {buttonLabel && ( + {footer && ( <> -
-
- - {buttonLabel} - -
-
+
{footer}
)} diff --git a/packages/wallet-widget/src/components/Select/WalletSelect.tsx b/packages/wallet-widget/src/components/Select/WalletSelect.tsx index 6064dad56..34929179c 100644 --- a/packages/wallet-widget/src/components/Select/WalletSelect.tsx +++ b/packages/wallet-widget/src/components/Select/WalletSelect.tsx @@ -1,19 +1,28 @@ -import { useWallets } from '@0xsequence/connect' -import { ChevronUpDownIcon, Text } from '@0xsequence/design-system' +import { truncateAtIndex, useWallets } from '@0xsequence/connect' +import { ChevronUpDownIcon, cn, GradientAvatar, Text } from '@0xsequence/design-system' import { useState } from 'react' -import { SelectWalletRow } from './SelectWalletRow.js' +import { ListCardWallet } from '../ListCard/ListCardWallet.js' + import { SlideupDrawer } from './SlideupDrawer.js' const WALLET_SELECT_HEIGHT = '60px' -export const WalletSelect = ({ selectedWallet, onClick }: { selectedWallet: string; onClick: (address: string) => void }) => { +export const WalletSelect = ({ + selectedWallet, + disabled = false, + onClick +}: { + selectedWallet: string + disabled?: boolean + onClick: (address: string) => void +}) => { const { wallets } = useWallets() const [isOpen, setIsOpen] = useState(false) const activeWallet = wallets.find(wallet => wallet.isActive) - const allButActiveWallet = wallets.filter(wallet => wallet.address !== activeWallet?.address) + const walletOptions = wallets const handleClick = (address: string) => { onClick(address) @@ -22,27 +31,42 @@ export const WalletSelect = ({ selectedWallet, onClick }: { selectedWallet: stri return (
setIsOpen(true)} + onClick={() => !disabled && setIsOpen(true)} >
- Select Connected Wallet + Wallet +
+ + + {truncateAtIndex(activeWallet?.address || '', 21)} + +
{isOpen && ( - setIsOpen(false)}> + + Wallets + + } + onClose={() => setIsOpen(false)} + >
- {allButActiveWallet.map(wallet => ( - ( + setIsOpen(false)} + onClick={() => handleClick(wallet.address)} /> ))}
diff --git a/packages/wallet-widget/src/components/SendItemInfo.tsx b/packages/wallet-widget/src/components/SendItemInfo.tsx index d56cf4b58..88ad44532 100644 --- a/packages/wallet-widget/src/components/SendItemInfo.tsx +++ b/packages/wallet-widget/src/components/SendItemInfo.tsx @@ -5,7 +5,7 @@ import { formatUnits } from 'viem' import { useSettings } from '../hooks/index.js' -import { CollectibleTileImage } from './CollectibleTileImage.js' +import { TokenTileImage } from './TokenTileImage.js' interface SendItemInfoProps { name: string @@ -57,7 +57,7 @@ export const SendItemInfo = ({
{showSquareImage ? (
- +
) : ( diff --git a/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SharedProvider.tsx b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SharedProvider.tsx new file mode 100644 index 000000000..86bf3ba5e --- /dev/null +++ b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SharedProvider.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState, type ReactNode } from 'react' +import { useAccount } from 'wagmi' + +import { SharedContextProvider } from '../../../contexts/Shared.js' + +export const SharedProvider = ({ children }: { children: ReactNode }) => { + const { connector } = useAccount() + + const [isGuest, setIsGuest] = useState(false) + const [signInDisplay, setSignInDisplay] = useState('') + const [sequenceWaasAccounts, setSequenceWaasAccounts] = useState<{ accounts: { email: string; type: string }[] } | null>(null) + + useEffect(() => { + const fetchSequenceWaasAccounts = async () => { + const sequenceWaas = connector?.sequenceWaas as { + listAccounts: () => Promise<{ accounts: { email: string; type: string }[] }> + } + const sequenceWaasAccounts = await sequenceWaas.listAccounts() + setSequenceWaasAccounts(sequenceWaasAccounts) + } + fetchSequenceWaasAccounts() + }, [connector]) + + useEffect(() => { + if (sequenceWaasAccounts) { + const isGuestAccount = sequenceWaasAccounts.accounts.some(account => account.type === 'Guest') + setIsGuest(isGuestAccount) + + if (isGuestAccount) { + setSignInDisplay('Guest') + return + } + + const waasEmail = sequenceWaasAccounts.accounts.find(account => account.type === 'OIDC')?.email + let backupEmail = '' + if (sequenceWaasAccounts.accounts.length > 0) { + backupEmail = sequenceWaasAccounts.accounts[0].email + } + setSignInDisplay(waasEmail || backupEmail) + } + }, [sequenceWaasAccounts]) + + return {children} +} diff --git a/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SwapProvider.tsx b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SwapProvider.tsx index 49b891771..6f2af9191 100644 --- a/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SwapProvider.tsx +++ b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SwapProvider.tsx @@ -1,262 +1,50 @@ -import type { LifiSwapQuote } from '@0xsequence/api' -import { getNativeTokenInfoByChainId, sendTransactions } from '@0xsequence/connect' -import { compareAddress, useToast } from '@0xsequence/design-system' -import { useAPIClient, useIndexerClient } from '@0xsequence/hooks' -import { useEffect, useState, type ReactNode } from 'react' -import { formatUnits, zeroAddress, type Hex } from 'viem' -import { useAccount, useChainId, useChains, usePublicClient, useWalletClient } from 'wagmi' +import type { Token } from '@0xsequence/api' +import { useWalletSettings } from '@0xsequence/connect' +import { useAPIClient } from '@0xsequence/hooks' +import type { ChainId } from '@0xsequence/network' +import { useEffect, useMemo, useState, type ReactNode } from 'react' +import { useChains } from 'wagmi' import { SwapContextProvider } from '../../../contexts/Swap.js' -import { useNavigation } from '../../../hooks/index.js' -import type { TokenBalanceWithPrice } from '../../../utils/index.js' export const SwapProvider = ({ children }: { children: ReactNode }) => { - const toast = useToast() - const { address: userAddress, connector } = useAccount() - const { setNavigation } = useNavigation() const apiClient = useAPIClient() - const connectedChainId = useChainId() const chains = useChains() - const [fromCoin, _setFromCoin] = useState() - const [toCoin, _setToCoin] = useState() - const [amount, _setAmount] = useState(0) - const [nonRecentAmount, setNonRecentAmount] = useState(0) - const [recentInput, setRecentInput] = useState<'from' | 'to'>('from') - const [isSwapReady, setIsSwapReady] = useState(false) - const [swapQuoteData, setSwapQuoteData] = useState() - const [isSwapQuotePending, setIsSwapQuotePending] = useState(false) - const [hasInsufficientFunds, setHasInsufficientFunds] = useState(false) - const [isErrorSwapQuote, setIsErrorSwapQuote] = useState(false) + const [lifiChains, setLifiChains] = useState([]) + const [lifiTokens, setLifiTokens] = useState([]) - const [isTxnPending, setIsTxnPending] = useState(false) - const [isErrorTxn, setIsErrorTxn] = useState(false) + const { readOnlyNetworks, displayedAssets } = useWalletSettings() - const publicClient = usePublicClient({ chainId: connectedChainId }) - const { data: walletClient } = useWalletClient({ chainId: connectedChainId }) - const indexerClient = useIndexerClient(connectedChainId) - - const resetSwapStates = () => { - _setFromCoin(undefined) - _setToCoin(undefined) - _setAmount(0) - setNonRecentAmount(0) - setRecentInput('from') - setIsSwapReady(false) - setSwapQuoteData(undefined) - setIsSwapQuotePending(false) - setIsErrorSwapQuote(false) - setIsTxnPending(false) - setIsErrorTxn(false) - } - - useEffect(() => { - resetSwapStates() - }, [userAddress, connectedChainId]) - - useEffect(() => { - setIsSwapReady(false) - setSwapQuoteData(undefined) - setIsErrorSwapQuote(false) - }, [fromCoin, toCoin, amount]) + const allNetworks = useMemo( + () => [ + ...new Set([...chains.map(chain => chain.id), ...(readOnlyNetworks || []), ...displayedAssets.map(asset => asset.chainId)]) + ], + [chains, readOnlyNetworks, displayedAssets] + ) useEffect(() => { - const fetchSwapQuote = async () => { - if (!fromCoin || !toCoin || amount === 0) { - return - } - - setIsSwapQuotePending(true) - setIsErrorSwapQuote(false) - - let swapQuote - try { - // swapQuote = await apiClient.getSwapQuoteV2({ - // userAddress: String(userAddress), - // buyCurrencyAddress: toCoin.contractAddress, - // sellCurrencyAddress: fromCoin.contractAddress, - // chainId: connectedChainId, - // includeApprove: true - // ...(recentInput === 'from' ? {sellAmount: String(amount)} : {buyAmount: String(amount)}) - // }) - - // TODO: use commented out code when getSwapQuoteV2 is updated to include sellAmount - - swapQuote = await apiClient.getLifiSwapQuote({ - params: { - walletAddress: userAddress ?? '', - toTokenAddress: toCoin.contractAddress, - fromTokenAddress: fromCoin.contractAddress, - toTokenAmount: String(amount), - chainId: connectedChainId, - includeApprove: true, - slippageBps: 100 - } - }) - - const transactionValue = swapQuote?.quote.transactionValue || '0' - // TODO: change this to "amount" from return - - setNonRecentAmount(Number(transactionValue)) - - setSwapQuoteData(swapQuote?.quote) - setIsSwapReady(true) - } catch (error) { - const hasInsufficientFunds = (error as any).code === -4 - setHasInsufficientFunds(hasInsufficientFunds) - setIsErrorSwapQuote(true) - } - setIsSwapQuotePending(false) - } - - fetchSwapQuote() - }, [fromCoin, toCoin, amount]) - - const setFromCoin = (coin: TokenBalanceWithPrice | undefined) => { - if (coin?.chainId === toCoin?.chainId && coin?.contractAddress === toCoin?.contractAddress) { - switchCoinOrder() - } else { - _setFromCoin(coin) + const fetchLifiChains = async () => { + const lifiSupportedChains = await apiClient.getLifiChains() + const supportedChains = lifiSupportedChains.chains.filter(chain => allNetworks.includes(chain)) + setLifiChains(supportedChains) } - } + fetchLifiChains() + }, [apiClient, allNetworks]) - const setToCoin = (coin: TokenBalanceWithPrice | undefined) => { - if (coin?.chainId === fromCoin?.chainId && coin?.contractAddress === fromCoin?.contractAddress) { - switchCoinOrder() - } else { - _setToCoin(coin) - } - } - - const setAmount = (newAmount: number, type: 'from' | 'to') => { - if (type === recentInput) { - _setAmount(newAmount) - } else { - const tempAmount = amount - setRecentInput(recentInput === 'from' ? 'to' : 'from') - _setAmount(newAmount) - setNonRecentAmount(tempAmount) - } - } - - const switchCoinOrder = () => { - const tempFrom = fromCoin - const tempTo = toCoin - _setFromCoin(tempTo) - _setToCoin(tempFrom) - setRecentInput(recentInput === 'from' ? 'to' : 'from') - } - - const onSubmitSwap = async () => { - if (isErrorSwapQuote || !userAddress || !publicClient || !walletClient || !connector || !fromCoin || !toCoin || !amount) { - console.error('Please ensure validation before allowing users to submit a swap') - return - } - - setIsErrorTxn(false) - setIsTxnPending(true) - - try { - const isSwapNativeToken = compareAddress(zeroAddress, swapQuoteData?.currencyAddress || '') - - const getSwapTransactions = () => { - if (!swapQuoteData) { - return [] - } - - const swapTransactions = [ - // Swap quote optional approve step - ...(swapQuoteData?.approveData && !isSwapNativeToken - ? [ - { - to: swapQuoteData?.currencyAddress as Hex, - data: swapQuoteData?.approveData as Hex, - chain: connectedChainId - } - ] - : []), - // Swap quote tx - { - to: swapQuoteData?.to as Hex, - data: swapQuoteData?.transactionData as Hex, - chain: connectedChainId, - ...(isSwapNativeToken - ? { - value: BigInt(swapQuoteData?.transactionValue || '0') - } - : {}) - } - ] - return swapTransactions - } - - const walletClientChainId = await walletClient.getChainId() - if (walletClientChainId !== connectedChainId) { - await walletClient.switchChain({ id: connectedChainId }) - } - - const isFromCoinNative = fromCoin.contractType === 'NATIVE' - const isToCoinNative = toCoin.contractType === 'NATIVE' - const fromCoinNativeInfo = getNativeTokenInfoByChainId(fromCoin.chainId, chains) - const toCoinNativeInfo = getNativeTokenInfoByChainId(toCoin.chainId, chains) - const toastFromCoinName = isFromCoinNative ? fromCoinNativeInfo.symbol : fromCoin.contractInfo?.symbol - const toastToCoinName = isToCoinNative ? toCoinNativeInfo.symbol : toCoin.contractInfo?.symbol - const toastFromAmount = formatUnits( - BigInt(recentInput === 'from' ? amount : nonRecentAmount), - (isFromCoinNative ? fromCoinNativeInfo.decimals : fromCoin.contractInfo?.decimals) || 18 - ) - const toastToAmount = formatUnits( - BigInt(recentInput === 'from' ? nonRecentAmount : amount), - (isToCoinNative ? toCoinNativeInfo.decimals : toCoin.contractInfo?.decimals) || 18 - ) - - await sendTransactions({ - connector, - walletClient, - publicClient, - chainId: connectedChainId, - indexerClient, - senderAddress: userAddress, - transactions: [...getSwapTransactions()] - }) - - toast({ - title: 'Transaction sent', - description: `Successfully swapped ${toastFromAmount} ${toastFromCoinName} for ${toastToAmount} ${toastToCoinName}`, - variant: 'success' - }) - - setNavigation({ - location: 'home' - }) - } catch (error) { - console.error('Failed to send transactions', error) - setIsSwapReady(false) - setIsTxnPending(false) - setIsErrorTxn(true) + useEffect(() => { + const fetchLifiTokens = async () => { + const tokens = await apiClient.getLifiTokens({ chainIds: lifiChains }) + setLifiTokens(tokens.tokens) } - } + fetchLifiTokens() + }, [apiClient, lifiChains]) return ( {children} diff --git a/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/FiatWalletsMapProvider.tsx b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/ValueRegistryProvider.tsx similarity index 67% rename from packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/FiatWalletsMapProvider.tsx rename to packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/ValueRegistryProvider.tsx index a541949ab..3278030a4 100644 --- a/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/FiatWalletsMapProvider.tsx +++ b/packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/ValueRegistryProvider.tsx @@ -3,20 +3,20 @@ import { useGetCoinPrices, useGetExchangeRate } from '@0xsequence/hooks' import { useEffect, useState, type ReactNode } from 'react' import { getAddress, zeroAddress } from 'viem' -import { FiatWalletsMapContextProvider, type FiatWalletPair } from '../../../contexts/index.js' +import { ValueRegistryContextProvider, type ValueRegistryPair } from '../../../contexts/index.js' import { useGetAllTokensDetails, useSettings } from '../../../hooks/index.js' import { computeBalanceFiat } from '../../../utils/index.js' -// Define the provider component -export const FiatWalletsMapProvider = ({ children }: { children: ReactNode }) => { +export const ValueRegistryProvider = ({ children }: { children: ReactNode }) => { const { wallets } = useWallets() - const { selectedNetworks, hideUnlistedTokens, fiatCurrency } = useSettings() + const { allNetworks, hideUnlistedTokens, fiatCurrency } = useSettings() - const [fiatWalletsMap, setFiatWalletsMap] = useState([]) + const [valueRegistryMap, setValueRegistryMap] = useState([]) + const [totalValue, setTotalValue] = useState('0') const { data: tokenBalancesData, isLoading: isTokenBalancesLoading } = useGetAllTokensDetails({ accountAddresses: wallets.map(wallet => wallet.address), - chainIds: selectedNetworks, + chainIds: allNetworks, hideUnlistedTokens }) @@ -41,7 +41,7 @@ export const FiatWalletsMapProvider = ({ children }: { children: ReactNode }) => coinPrices.length > 0 && conversionRate ) { - const newFiatWalletsMap = wallets.map(wallet => { + const newValueRegistryMap = wallets.map(wallet => { const walletBalances = coinBalancesUnordered.filter(b => getAddress(b.accountAddress) === getAddress(wallet.address)) const walletFiatValue = walletBalances.reduce((acc, coin) => { return ( @@ -58,15 +58,17 @@ export const FiatWalletsMapProvider = ({ children }: { children: ReactNode }) => }, 0) return { accountAddress: wallet.address, - fiatValue: walletFiatValue.toFixed(2) - } as FiatWalletPair + value: walletFiatValue.toFixed(2) + } as ValueRegistryPair }) - if (JSON.stringify(newFiatWalletsMap) !== JSON.stringify(fiatWalletsMap)) { - setFiatWalletsMap(newFiatWalletsMap) + if (JSON.stringify(newValueRegistryMap) !== JSON.stringify(valueRegistryMap)) { + setValueRegistryMap(newValueRegistryMap) + const totalValue = newValueRegistryMap.reduce((acc, wallet) => acc + Number(wallet.value), 0).toFixed(2) + setTotalValue(totalValue) } } }, [coinBalancesUnordered, coinPrices, conversionRate]) - return {children} + return {children} } diff --git a/packages/wallet-widget/src/components/SequenceWalletProvider/SequenceWalletProvider.tsx b/packages/wallet-widget/src/components/SequenceWalletProvider/SequenceWalletProvider.tsx index 64d5fe668..d8a036ba3 100644 --- a/packages/wallet-widget/src/components/SequenceWalletProvider/SequenceWalletProvider.tsx +++ b/packages/wallet-widget/src/components/SequenceWalletProvider/SequenceWalletProvider.tsx @@ -1,13 +1,19 @@ 'use client' import { SequenceCheckoutProvider, useAddFundsModal } from '@0xsequence/checkout' -import { getModalPositionCss, ShadowRoot, useConnectConfigContext, useOpenConnectModal, useTheme } from '@0xsequence/connect' +import { + getModalPositionCss, + ShadowRoot, + useConnectConfigContext, + useOpenConnectModal, + useSocialLink, + useTheme +} from '@0xsequence/connect' import { Modal, Scroll, ToastProvider } from '@0xsequence/design-system' import { AnimatePresence } from 'motion/react' -import React, { useContext, useEffect, useState, type ReactNode } from 'react' +import { useContext, useEffect, useState, type ReactNode } from 'react' import { useAccount } from 'wagmi' -import { HEADER_HEIGHT, HEADER_HEIGHT_WITH_LABEL } from '../../constants/index.js' import { WALLET_HEIGHT, WALLET_WIDTH } from '../../constants/index.js' import { NavigationContextProvider, @@ -16,10 +22,12 @@ import { type Navigation, type WalletOptions } from '../../contexts/index.js' +import { NavigationHeaderContextProvider } from '../../contexts/NavigationHeader.js' import { WalletContentRefContext, WalletContentRefProvider } from '../../contexts/WalletContentRef.js' -import { FiatWalletsMapProvider } from './ProviderComponents/FiatWalletsMapProvider.js' +import { SharedProvider } from './ProviderComponents/SharedProvider.js' import { SwapProvider } from './ProviderComponents/SwapProvider.js' +import { ValueRegistryProvider } from './ProviderComponents/ValueRegistryProvider.js' import { getContent, getHeader } from './utils/index.js' export type SequenceWalletProviderProps = { @@ -44,6 +52,7 @@ export const WalletContent = ({ children }: SequenceWalletProviderProps) => { const { theme, position } = useTheme() const { isAddFundsModalOpen } = useAddFundsModal() const { isConnectModalOpen } = useOpenConnectModal() + const { isSocialLinkOpen } = useSocialLink() const { address } = useAccount() const { customCSS } = useConnectConfigContext() @@ -68,72 +77,78 @@ export const WalletContent = ({ children }: SequenceWalletProviderProps) => { const [isBackButtonEnabled, setIsBackButtonEnabled] = useState(true) const navigation = history.length > 0 ? history[history.length - 1] : DEFAULT_LOCATION + // Navigation Header Context + const [search, setSearch] = useState('') + const [selectedTab, setSelectedTab] = useState<'tokens' | 'collectibles' | 'history'>('tokens') + const displayScrollbar = + navigation.location === 'home' || navigation.location === 'send-general' || navigation.location === 'collectible-details' || navigation.location === 'coin-details' || - navigation.location === 'history' || + navigation.location === 'collection-details' || + navigation.location === 'transaction-details' || + navigation.location === 'swap' || navigation.location === 'search' || - navigation.location === 'search-view-all' || navigation.location === 'settings-wallets' || - navigation.location === 'settings-networks' || navigation.location === 'settings-currency' || navigation.location === 'settings-profiles' || - navigation.location === 'settings-apps' || - navigation.location === 'legacy-settings-currency' || - navigation.location === 'search-tokens' || - navigation.location === 'search-collectibles' - - let paddingTop = '0px' - switch (navigation.location) { - case 'send-general': - paddingTop = HEADER_HEIGHT_WITH_LABEL - break - default: - paddingTop = HEADER_HEIGHT - } + navigation.location === 'settings-apps' const walletContentRef = useContext(WalletContentRefContext) return ( - - - - - - {openWalletModal && !isAddFundsModalOpen && !isConnectModalOpen && ( - setOpenWalletModal(false)} - > -
- {getHeader(navigation)} - - {displayScrollbar ? ( - {getContent(navigation)} - ) : ( - getContent(navigation) - )} -
-
- )} -
-
- {children} -
-
-
+ + + + + + + + {openWalletModal && !isAddFundsModalOpen && !isConnectModalOpen && !isSocialLinkOpen && ( + setOpenWalletModal(false)} + > +
+
{getHeader(navigation)}
+ +
+ {displayScrollbar ? ( + {getContent(navigation)} + ) : ( + getContent(navigation) + )} +
+
+
+ )} +
+
+ {children} +
+
+
+
+
) diff --git a/packages/wallet-widget/src/components/SequenceWalletProvider/utils/index.tsx b/packages/wallet-widget/src/components/SequenceWalletProvider/utils/index.tsx index 709639995..6ca80bcb3 100644 --- a/packages/wallet-widget/src/components/SequenceWalletProvider/utils/index.tsx +++ b/packages/wallet-widget/src/components/SequenceWalletProvider/utils/index.tsx @@ -1,19 +1,18 @@ import type { Navigation } from '../../../contexts/index.js' +import { CollectionDetails } from '../../../views/CollectionDetails/index.js' import { + Buy, CoinDetails, CollectibleDetails, - History, Home, Receive, - SearchCollectibles, - SearchTokens, + Search, SendCoin, SendCollectible, SendGeneral, SettingsApps, SettingsCurrency, SettingsMenu, - SettingsNetworks, SettingsPreferences, SettingsProfiles, SettingsWallets, @@ -24,7 +23,6 @@ import { TransactionDetails } from '../../../views/index.js' import { NavigationHeader } from '../../NavigationHeader/index.js' -import { WalletHeader } from '../../WalletHeader/index.js' export const getContent = (navigation: Navigation) => { const { location } = navigation @@ -46,23 +44,14 @@ export const getContent = (navigation: Navigation) => { return case 'receive': return - case 'history': - return - case 'search-tokens': - return - case 'search-collectibles': - return ( - - ) + case 'buy': + return + case 'search': + return case 'settings': return case 'settings-wallets': return - case 'settings-networks': - return case 'settings-currency': return case 'settings-profiles': @@ -91,6 +80,8 @@ export const getContent = (navigation: Navigation) => { accountAddress={navigation.params.accountAddress} /> ) + case 'collection-details': + return case 'transaction-details': return case 'swap-coin': @@ -112,46 +103,69 @@ export const getContent = (navigation: Navigation) => { export const getHeader = (navigation: Navigation) => { const { location } = navigation switch (location) { - case 'search-tokens': - return - case 'search-collectibles': - return + case 'home': + return case 'settings': - return + return case 'settings-wallets': - return - case 'settings-networks': - return + return case 'settings-currency': - return + return case 'settings-profiles': - return + return case 'settings-preferences': - return + return case 'settings-apps': - return + return case 'connect-dapp': - return - case 'history': - return + return case 'coin-details': - return + return ( + + ) case 'collectible-details': - return + return ( + + ) + case 'collection-details': + return ( + + ) case 'transaction-details': - return + return case 'send-general': - return case 'send-coin': - return case 'send-collectible': - return + return case 'swap': - return case 'swap-coin': case 'swap-coin-list': - return + return case 'receive': - return + return + case 'buy': + return + case 'search': + return } } diff --git a/packages/wallet-widget/src/components/TokenTileImage.tsx b/packages/wallet-widget/src/components/TokenTileImage.tsx new file mode 100644 index 000000000..bfea9ec5a --- /dev/null +++ b/packages/wallet-widget/src/components/TokenTileImage.tsx @@ -0,0 +1,34 @@ +import { Card, Image, Text } from '@0xsequence/design-system' + +interface TokenTileImageProps { + src?: string + symbol?: string +} + +export const TokenTileImage = ({ src, symbol }: TokenTileImageProps) => { + let symbolLabel + + const abrevSymbol = symbol + ?.split(' ') + .map(word => word[0]) + .join('') + const shortSymbol = symbol?.replace(/\s/, '').slice(0, 6) + + if (abrevSymbol && abrevSymbol.length > 2) { + symbolLabel = abrevSymbol + } else { + symbolLabel = shortSymbol + } + + return ( + + {src ? ( + + ) : ( + + {symbolLabel} + + )} + + ) +} diff --git a/packages/wallet-widget/src/components/TransactionHistoryList/TransactionHistoryItem.tsx b/packages/wallet-widget/src/components/TransactionHistoryList/TransactionHistoryItem.tsx index bf5a83b03..b94958901 100644 --- a/packages/wallet-widget/src/components/TransactionHistoryList/TransactionHistoryItem.tsx +++ b/packages/wallet-widget/src/components/TransactionHistoryList/TransactionHistoryItem.tsx @@ -121,6 +121,7 @@ export const TransactionHistoryItem = ({ transaction }: TransactionHistoryItemPr const getTransfer = ({ transfer, isFirstItem }: GetTransfer) => { const { amounts } = transfer const date = dayjs(transaction.timestamp).format('MMM DD, YYYY') + return (
diff --git a/packages/wallet-widget/src/components/TransactionHistoryList/index.tsx b/packages/wallet-widget/src/components/TransactionHistoryList/index.tsx index 944a75bf3..ed2308145 100644 --- a/packages/wallet-widget/src/components/TransactionHistoryList/index.tsx +++ b/packages/wallet-widget/src/components/TransactionHistoryList/index.tsx @@ -2,6 +2,8 @@ import { Spinner, Text } from '@0xsequence/design-system' import type { Transaction } from '@0xsequence/indexer' import { useMemo } from 'react' +import { NoResults } from '../NoResults.js' + import { TransactionHistoryItem } from './TransactionHistoryItem.js' import { TransactionHistorySkeleton } from './TransactionHistorySkeleton.js' @@ -121,7 +123,7 @@ export const TransactionHistoryList = ({ transactions, isLoading, isFetchingNext
{transactions.map((transaction, index) => { return ( -
+
) @@ -144,12 +146,7 @@ export const TransactionHistoryList = ({ transactions, isLoading, isFetchingNext
) })} - {transactions.length === 0 && ( -
- - No Recent Transaction History Found -
- )} + {transactions.length === 0 && } {isFetchingNextPage && (
diff --git a/packages/wallet-widget/src/components/WalletAccountGradient.tsx b/packages/wallet-widget/src/components/WalletAccountGradient.tsx index 087bd66b0..cd1f47c23 100644 --- a/packages/wallet-widget/src/components/WalletAccountGradient.tsx +++ b/packages/wallet-widget/src/components/WalletAccountGradient.tsx @@ -1,41 +1,51 @@ import { useWallets } from '@0xsequence/connect' -import { GradientAvatar } from '@0xsequence/design-system' +import { GradientAvatar, useMediaQuery } from '@0xsequence/design-system' import { getConnectorLogo } from './ConnectorLogos/getConnectorLogos.js' -export const WalletAccountGradient = ({ - accountAddress, - size = 'large' -}: { - accountAddress: string - size?: 'small' | 'large' -}) => { +export const WalletAccountGradient = ({ accountAddresses }: { accountAddresses: string[] }) => { + const isMobile = useMediaQuery('isMobile') + const limit = isMobile ? 4 : 6 + // limit the number of wallets shown to 6 on desktop and 4 on mobile + + return ( +
+ {accountAddresses.slice(0, limit).map((address, index) => ( + + ))} +
+ ) +} + +const WalletAccountGradientItem = ({ address, index }: { address: string; index: number }) => { const { wallets } = useWallets() - const remSize = size === 'small' ? 8 : 16 - const LoginIcon = getConnectorLogo(wallets.find(wallet => wallet.address === accountAddress)?.signInMethod || '') + const LoginIcon = getConnectorLogo(wallets.find(wallet => address.includes(wallet.address))?.signInMethod || '') return ( -
-
- +
+ + + {LoginIcon && (
-
{LoginIcon}
+
+ {LoginIcon} +
-
+ )}
) } diff --git a/packages/wallet-widget/src/components/WalletHeader/components/AccountInformation.tsx b/packages/wallet-widget/src/components/WalletHeader/components/AccountInformation.tsx deleted file mode 100644 index 83dca2043..000000000 --- a/packages/wallet-widget/src/components/WalletHeader/components/AccountInformation.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { formatAddress } from '@0xsequence/connect' -import { Card, ChevronUpDownIcon, GradientAvatar, Text } from '@0xsequence/design-system' -import { useAccount } from 'wagmi' - -interface AccountInformationProps { - onClickAccount?: () => void -} - -export const AccountInformation = ({ onClickAccount }: AccountInformationProps) => { - const { address } = useAccount() - - return ( - - - - {formatAddress(address || '')} - - {onClickAccount && } - - ) -} diff --git a/packages/wallet-widget/src/components/WalletHeader/index.tsx b/packages/wallet-widget/src/components/WalletHeader/index.tsx deleted file mode 100644 index 0ec0d7266..000000000 --- a/packages/wallet-widget/src/components/WalletHeader/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useOpenConnectModal, useWallets } from '@0xsequence/connect' -import { ChevronLeftIcon, IconButton, Text } from '@0xsequence/design-system' -import { AnimatePresence } from 'motion/react' -import { useState } from 'react' - -import { HEADER_HEIGHT, HEADER_HEIGHT_WITH_LABEL } from '../../constants/index.js' -import { useNavigation } from '../../hooks/index.js' -import { SelectWalletRow } from '../Select/SelectWalletRow.js' -import { SlideupDrawer } from '../Select/SlideupDrawer.js' - -import { AccountInformation } from './components/AccountInformation.js' - -export const WalletHeader = ({ - primaryText, - enableAccountSelector = false -}: { - primaryText?: string - enableAccountSelector?: boolean -}) => { - const { wallets, setActiveWallet } = useWallets() - const { setOpenConnectModal } = useOpenConnectModal() - const { goBack } = useNavigation() - - const [accountSelectorModalOpen, setAccountSelectorModalOpen] = useState(false) - - const onClickSelector = () => { - setAccountSelectorModalOpen(true) - } - - const handleAddNewWallet = () => { - setAccountSelectorModalOpen(false) - setOpenConnectModal(true) - } - - const onClickBack = () => { - goBack() - } - - return ( -
-
- - -
-
- {primaryText && ( - - {primaryText} - - )} - - {accountSelectorModalOpen && ( - setAccountSelectorModalOpen(false)} - label="Select main wallet" - buttonLabel="+ Add new wallet" - handleButtonPress={handleAddNewWallet} - dragHandleWidth={28} - > -
- {wallets.map((wallet, index) => ( - setAccountSelectorModalOpen(false)} - onClick={setActiveWallet} - /> - ))} -
-
- )} -
-
- ) -} diff --git a/packages/wallet-widget/src/constants/sizing.ts b/packages/wallet-widget/src/constants/sizing.ts index 1eea31b50..fa0adfb77 100644 --- a/packages/wallet-widget/src/constants/sizing.ts +++ b/packages/wallet-widget/src/constants/sizing.ts @@ -1,4 +1,3 @@ -export const HEADER_HEIGHT = '60px' -export const HEADER_HEIGHT_WITH_LABEL = '100px' +export const HEADER_HEIGHT = '68px' export const WALLET_WIDTH = '460px' -export const WALLET_HEIGHT = 'min(800px, 90vh)' +export const WALLET_HEIGHT = '644px' diff --git a/packages/wallet-widget/src/contexts/FiatWalletsMap.ts b/packages/wallet-widget/src/contexts/FiatWalletsMap.ts deleted file mode 100644 index 0e012a9a1..000000000 --- a/packages/wallet-widget/src/contexts/FiatWalletsMap.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createGenericContext } from './genericContext.js' - -export interface FiatWalletPair { - accountAddress: string - fiatValue: string -} - -export interface FiatWalletsMapContext { - fiatWalletsMap: FiatWalletPair[] - setFiatWalletsMap: (fiatWalletsMap: FiatWalletPair[]) => void -} - -const [useFiatWalletsMapContext, FiatWalletsMapContextProvider] = createGenericContext() - -export { FiatWalletsMapContextProvider, useFiatWalletsMapContext } diff --git a/packages/wallet-widget/src/contexts/Navigation.ts b/packages/wallet-widget/src/contexts/Navigation.ts index ab0a5b7af..ba72987af 100644 --- a/packages/wallet-widget/src/contexts/Navigation.ts +++ b/packages/wallet-widget/src/contexts/Navigation.ts @@ -27,6 +27,16 @@ export interface CollectibleDetailsNavigation { params: CollectibleDetailsParams } +export interface CollectionDetailsParams { + contractAddress: string + chainId: number +} + +export interface CollectionDetailsNavigation { + location: 'collection-details' + params: CollectionDetailsParams +} + export interface TransactionDetailsParams { transaction: Transaction } @@ -36,15 +46,6 @@ export interface TransactionDetailsNavigation { params: TransactionDetailsParams } -export interface SearchViewAllParams { - defaultTab: 'coins' | 'collections' -} - -export interface SearchViewAllNavigation { - location: 'search-view-all' - params: SearchViewAllParams -} - export interface SendCoinParams { chainId: number contractAddress: string @@ -87,47 +88,30 @@ export interface SendCollectibleNavigation { params: SendCollectibleParams } -export interface SearchCollectiblesParams { - selectedCollection: { - chainId: number - contractAddress: string - } -} - -export interface SearchCollectiblesNavigation { - location: 'search-collectibles' - params?: SearchCollectiblesParams -} export interface BasicNavigation { location: | 'home' | 'send-general' | 'swap' | 'receive' + | 'buy' | 'history' - | 'legacy-settings' - | 'legacy-settings-general' - | 'legacy-settings-currency' - | 'legacy-settings-networks' | 'settings' | 'settings-wallets' - | 'settings-networks' | 'settings-currency' | 'settings-profiles' | 'settings-apps' | 'settings-preferences' | 'connect-dapp' | 'search' - | 'search-tokens' } export type Navigation = | BasicNavigation | CoinDetailsNavigation | CollectibleDetailsNavigation + | CollectionDetailsNavigation | TransactionDetailsNavigation - | SearchCollectiblesNavigation - | SearchViewAllNavigation | SendCoinNavigation | SendCollectibleNavigation | SwapCoinNavigation diff --git a/packages/wallet-widget/src/contexts/NavigationHeader.ts b/packages/wallet-widget/src/contexts/NavigationHeader.ts new file mode 100644 index 000000000..d9c36287a --- /dev/null +++ b/packages/wallet-widget/src/contexts/NavigationHeader.ts @@ -0,0 +1,12 @@ +import { createGenericContext } from './genericContext.js' + +export interface NavigationHeaderContext { + search: string + selectedTab: 'tokens' | 'collectibles' | 'history' + setSearch: (search: string) => void + setSelectedTab: (tab: 'tokens' | 'collectibles' | 'history') => void +} + +const [useNavigationHeaderContext, NavigationHeaderContextProvider] = createGenericContext() + +export { NavigationHeaderContextProvider, useNavigationHeaderContext } diff --git a/packages/wallet-widget/src/contexts/Shared.ts b/packages/wallet-widget/src/contexts/Shared.ts new file mode 100644 index 000000000..606433544 --- /dev/null +++ b/packages/wallet-widget/src/contexts/Shared.ts @@ -0,0 +1,10 @@ +import { createGenericContext } from './genericContext.js' + +export interface SharedContext { + isGuest: boolean + signInDisplay: string +} + +const [useSharedContext, SharedContextProvider] = createGenericContext() + +export { SharedContextProvider, useSharedContext } diff --git a/packages/wallet-widget/src/contexts/Swap.ts b/packages/wallet-widget/src/contexts/Swap.ts index 11bd43ecd..ab435725c 100644 --- a/packages/wallet-widget/src/contexts/Swap.ts +++ b/packages/wallet-widget/src/contexts/Swap.ts @@ -1,25 +1,10 @@ -import type { TokenBalanceWithPrice } from '../utils/index.js' +import type { Token } from '@0xsequence/api' import { createGenericContext } from './genericContext.js' export interface SwapContext { - fromCoin: TokenBalanceWithPrice | undefined - toCoin: TokenBalanceWithPrice | undefined - amount: number - nonRecentAmount: number - recentInput: 'from' | 'to' - isSwapReady: boolean - isSwapQuotePending: boolean - hasInsufficientFunds: boolean - isErrorSwapQuote: boolean - isTxnPending: boolean - isErrorTxn: boolean - setFromCoin: (coin: TokenBalanceWithPrice | undefined) => void - setToCoin: (coin: TokenBalanceWithPrice | undefined) => void - setAmount: (amount: number, type: 'from' | 'to') => void - switchCoinOrder: () => void - onSubmitSwap: () => void - resetSwapStates: () => void + lifiChains: number[] + lifiTokens: Token[] } const [useSwapContext, SwapContextProvider] = createGenericContext() diff --git a/packages/wallet-widget/src/contexts/ValueRegistry.ts b/packages/wallet-widget/src/contexts/ValueRegistry.ts new file mode 100644 index 000000000..da40eb276 --- /dev/null +++ b/packages/wallet-widget/src/contexts/ValueRegistry.ts @@ -0,0 +1,15 @@ +import { createGenericContext } from './genericContext.js' + +export interface ValueRegistryPair { + accountAddress: string + value: string +} + +export interface ValueRegistryContext { + valueRegistryMap: ValueRegistryPair[] + totalValue: string +} + +const [useValueRegistryContext, ValueRegistryContextProvider] = createGenericContext() + +export { useValueRegistryContext, ValueRegistryContextProvider } diff --git a/packages/wallet-widget/src/contexts/index.ts b/packages/wallet-widget/src/contexts/index.ts index 8d5fde025..669c31a62 100644 --- a/packages/wallet-widget/src/contexts/index.ts +++ b/packages/wallet-widget/src/contexts/index.ts @@ -1,3 +1,3 @@ export * from './WalletModal.js' export * from './Navigation.js' -export * from './FiatWalletsMap.js' +export * from './ValueRegistry.js' diff --git a/packages/wallet-widget/src/hooks/index.ts b/packages/wallet-widget/src/hooks/index.ts index 0014bdff7..7f9f8a2e2 100644 --- a/packages/wallet-widget/src/hooks/index.ts +++ b/packages/wallet-widget/src/hooks/index.ts @@ -1,7 +1,7 @@ export * from './useOpenWalletModal.js' export * from './useNavigation.js' export * from './useSettings.js' -export * from './useFiatWalletsMap.js' +export * from './useValueRegistry.js' export * from './useGetAllTokensDetails.js' export * from './useGetCollections.js' export * from './useGetMoreBalances.js' diff --git a/packages/wallet-widget/src/hooks/useFiatWalletsMap.ts b/packages/wallet-widget/src/hooks/useFiatWalletsMap.ts deleted file mode 100644 index e8cdd7d0a..000000000 --- a/packages/wallet-widget/src/hooks/useFiatWalletsMap.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useFiatWalletsMapContext } from '../contexts/FiatWalletsMap.js' - -export const useFiatWalletsMap = () => { - const { fiatWalletsMap, setFiatWalletsMap } = useFiatWalletsMapContext() - - return { fiatWalletsMap, setFiatWalletsMap } -} diff --git a/packages/wallet-widget/src/hooks/useGetAllCollections.tsx b/packages/wallet-widget/src/hooks/useGetAllCollections.tsx new file mode 100644 index 000000000..b44bfc9b9 --- /dev/null +++ b/packages/wallet-widget/src/hooks/useGetAllCollections.tsx @@ -0,0 +1,51 @@ +import { useGetMultipleContractsInfo, useGetTokenBalancesSummary } from '@0xsequence/hooks' +import { ContractVerificationStatus } from '@0xsequence/indexer' +import { useEffect } from 'react' + +export const useGetAllCollections = ({ + accountAddresses, + chainIds, + hideUnlistedTokens +}: { + accountAddresses: string[] + chainIds: number[] + hideUnlistedTokens: boolean +}) => { + const { + data: tokenBalancesData, + isLoading, + fetchNextPage, + hasNextPage, + isFetchingNextPage + } = useGetTokenBalancesSummary({ + chainIds, + filter: { + accountAddresses, + contractStatus: hideUnlistedTokens ? ContractVerificationStatus.VERIFIED : ContractVerificationStatus.ALL, + omitNativeBalances: false + }, + page: { pageSize: 40 } + }) + + useEffect(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage() + } + }, [hasNextPage, isFetchingNextPage]) + + const collections = tokenBalancesData?.pages.flatMap(page => + page.balances.filter(balance => balance.contractType === 'ERC721' || balance.contractType === 'ERC1155') + ) + + const { data: collectionsWithMetadata } = useGetMultipleContractsInfo( + collections?.map(collection => ({ + chainID: collection.chainId.toString(), + contractAddress: collection.contractAddress + })) || [] + ) + + return { + data: collectionsWithMetadata || [], + isLoading: isLoading || isFetchingNextPage + } +} diff --git a/packages/wallet-widget/src/hooks/useGetAllTokensDetails.ts b/packages/wallet-widget/src/hooks/useGetAllTokensDetails.ts index dc8e450b0..9aa5724a7 100644 --- a/packages/wallet-widget/src/hooks/useGetAllTokensDetails.ts +++ b/packages/wallet-widget/src/hooks/useGetAllTokensDetails.ts @@ -5,12 +5,10 @@ import { useEffect } from 'react' export const useGetAllTokensDetails = ({ accountAddresses, chainIds, - contractWhitelist, hideUnlistedTokens }: { accountAddresses: string[] chainIds: number[] - contractWhitelist?: string[] hideUnlistedTokens: boolean }) => { const { @@ -24,7 +22,6 @@ export const useGetAllTokensDetails = ({ filter: { accountAddresses, contractStatus: hideUnlistedTokens ? ContractVerificationStatus.VERIFIED : ContractVerificationStatus.ALL, - contractWhitelist: contractWhitelist ?? [], omitNativeBalances: false }, page: { pageSize: 40 } @@ -38,6 +35,6 @@ export const useGetAllTokensDetails = ({ return { data: tokenBalancesData?.pages.flatMap(page => page.balances) || [], - isLoading: isLoading + isLoading: isLoading || isFetchingNextPage } } diff --git a/packages/wallet-widget/src/hooks/useGetCollections.ts b/packages/wallet-widget/src/hooks/useGetCollections.ts index 62b353b2f..9dc9c8917 100644 --- a/packages/wallet-widget/src/hooks/useGetCollections.ts +++ b/packages/wallet-widget/src/hooks/useGetCollections.ts @@ -51,6 +51,6 @@ export const useGetCollections = ({ return { data: uniqueCollections, - isLoading + isLoading: isLoading || isFetchingNextPage } } diff --git a/packages/wallet-widget/src/hooks/useGetMoreBalances.ts b/packages/wallet-widget/src/hooks/useGetMoreBalances.ts index cd429b933..dcd614a3e 100644 --- a/packages/wallet-widget/src/hooks/useGetMoreBalances.ts +++ b/packages/wallet-widget/src/hooks/useGetMoreBalances.ts @@ -1,13 +1,11 @@ import { useInfiniteQuery } from '@tanstack/react-query' import type { InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query' -import type { TokenBalanceWithPrice } from '../utils/index.js' - export const useGetMoreBalances = ( - balances: TokenBalanceWithPrice[], + balances: any[], pageSize: number, options?: { enabled: boolean } -): UseInfiniteQueryResult, Error> => { +): UseInfiniteQueryResult, Error> => { return useInfiniteQuery({ queryKey: ['infiniteBalances', balances], queryFn: ({ pageParam }) => { diff --git a/packages/wallet-widget/src/hooks/useNavigationHeader.ts b/packages/wallet-widget/src/hooks/useNavigationHeader.ts new file mode 100644 index 000000000..b830233ec --- /dev/null +++ b/packages/wallet-widget/src/hooks/useNavigationHeader.ts @@ -0,0 +1,6 @@ +import { useNavigationHeaderContext } from '../contexts/NavigationHeader.js' + +export const useNavigationHeader = () => { + const { search, selectedTab, setSearch, setSelectedTab } = useNavigationHeaderContext() + return { search, selectedTab, setSearch, setSelectedTab } +} diff --git a/packages/wallet-widget/src/hooks/useSettings.ts b/packages/wallet-widget/src/hooks/useSettings.ts index 1cecac080..2458e5633 100644 --- a/packages/wallet-widget/src/hooks/useSettings.ts +++ b/packages/wallet-widget/src/hooks/useSettings.ts @@ -21,17 +21,17 @@ interface Settings { selectedNetworks: number[] allNetworks: number[] selectedWallets: ConnectedWallet[] - selectedCollections: SettingsCollection[] + showCollections: boolean hideUnlistedTokensObservable: Observable fiatCurrencyObservable: Observable selectedNetworksObservable: Observable selectedWalletsObservable: Observable - selectedCollectionsObservable: Observable + showCollectionsObservable: Observable setFiatCurrency: (newFiatCurrency: FiatCurrency) => void setHideUnlistedTokens: (newState: boolean) => void setSelectedWallets: (newWallets: ConnectedWallet[]) => void setSelectedNetworks: (newNetworks: number[]) => void - setSelectedCollections: (newCollections: SettingsCollection[]) => void + setShowCollections: (newState: boolean) => void } type SettingsItems = { @@ -39,7 +39,7 @@ type SettingsItems = { fiatCurrencyObservable: MutableObservable selectedWalletsObservable: MutableObservable selectedNetworksObservable: MutableObservable - selectedCollectionsObservable: MutableObservable + showCollectionsObservable: MutableObservable } let settingsObservables: SettingsItems | null = null @@ -89,7 +89,7 @@ export const useSettings = (): Settings => { let fiatCurrency = defaultFiatCurrency let selectedWallets: ConnectedWallet[] = allWallets let selectedNetworks: number[] = allNetworks - let selectedCollections: SettingsCollection[] = [] + let showCollections = false try { const settingsStorage = localStorage.getItem(LocalStorageKey.Settings) @@ -125,15 +125,13 @@ export const useSettings = (): Settings => { } }) - const hasInvalidNetworksSelection = selectedNetworks.length > 1 && selectedNetworks.length !== allNetworks.length - - if (hasInvalidNetworks || hasInvalidNetworksSelection) { + if (hasInvalidNetworks) { selectedNetworks = allNetworks localStorage.setItem(LocalStorageKey.Settings, JSON.stringify({ ...settings, selectedNetworks: allNetworks })) } } - if (settings?.selectedCollections !== undefined) { - selectedCollections = settings?.selectedCollections + if (settings?.showCollections !== undefined) { + showCollections = settings?.showCollections } } catch (e) { console.error(e, 'Failed to fetch settings') @@ -144,23 +142,20 @@ export const useSettings = (): Settings => { fiatCurrencyObservable: observable(fiatCurrency), selectedWalletsObservable: observable(selectedWallets), selectedNetworksObservable: observable(selectedNetworks), - selectedCollectionsObservable: observable(selectedCollections) + showCollectionsObservable: observable(showCollections) } } const resetSettings = () => { if (settingsObservables) { const selectedWallets = settingsObservables.selectedWalletsObservable.get() - const selectedNetworks = settingsObservables.selectedNetworksObservable.get() const isPartialSelection = selectedWallets.length > 1 && selectedWallets.length !== allWallets.length const hasInvalidWallets = selectedWallets.some(wallet => !allWallets.some((w: ConnectedWallet) => w.address === wallet.address)) || isPartialSelection - const hasInvalidNetworksSelection = selectedNetworks.length > 1 && selectedNetworks.length !== allNetworks.length - - if (hasInvalidWallets || hasInvalidNetworksSelection || !selectedWallets.length) { + if (hasInvalidWallets || !selectedWallets.length) { return true } } @@ -176,7 +171,7 @@ export const useSettings = (): Settings => { fiatCurrencyObservable, selectedWalletsObservable, selectedNetworksObservable, - selectedCollectionsObservable + showCollectionsObservable } = settingsObservables const setHideUnlistedTokens = (newState: boolean) => { @@ -203,17 +198,12 @@ export const useSettings = (): Settings => { selectedNetworksObservable.set(allNetworks) } else { selectedNetworksObservable.set(newSelectedNetworks) - selectedCollectionsObservable.set([]) } updateLocalStorage() } - const setSelectedCollections = (newSelectedCollections: SettingsCollection[]) => { - if (newSelectedCollections.length === 0) { - selectedCollectionsObservable.set([]) - } else { - selectedCollectionsObservable.set(newSelectedCollections) - } + const setShowCollections = (newState: boolean) => { + showCollectionsObservable.set(newState) updateLocalStorage() } @@ -223,7 +213,7 @@ export const useSettings = (): Settings => { fiatCurrency: fiatCurrencyObservable.get(), selectedWallets: selectedWalletsObservable.get(), selectedNetworks: selectedNetworksObservable.get(), - selectedCollections: selectedCollectionsObservable.get() + showCollections: showCollectionsObservable.get() } console.log('settings updated', newSettings) localStorage.setItem(LocalStorageKey.Settings, JSON.stringify(newSettings)) @@ -235,16 +225,16 @@ export const useSettings = (): Settings => { selectedWallets: selectedWalletsObservable.get(), selectedNetworks: selectedNetworksObservable.get(), allNetworks: allNetworks, - selectedCollections: selectedCollectionsObservable.get(), + showCollections: showCollectionsObservable.get(), hideUnlistedTokensObservable, fiatCurrencyObservable, selectedWalletsObservable, selectedNetworksObservable, - selectedCollectionsObservable, + showCollectionsObservable, setFiatCurrency, setHideUnlistedTokens, setSelectedWallets, setSelectedNetworks, - setSelectedCollections + setShowCollections } } diff --git a/packages/wallet-widget/src/hooks/useShared.ts b/packages/wallet-widget/src/hooks/useShared.ts new file mode 100644 index 000000000..b0a405518 --- /dev/null +++ b/packages/wallet-widget/src/hooks/useShared.ts @@ -0,0 +1,7 @@ +import { useSharedContext } from '../contexts/Shared.js' + +export const useShared = () => { + const { isGuest, signInDisplay } = useSharedContext() + + return { isGuest, signInDisplay } +} diff --git a/packages/wallet-widget/src/hooks/useSwap.tsx b/packages/wallet-widget/src/hooks/useSwap.tsx index 2192ddfe3..bfe7a7ad9 100644 --- a/packages/wallet-widget/src/hooks/useSwap.tsx +++ b/packages/wallet-widget/src/hooks/useSwap.tsx @@ -1,43 +1,10 @@ import { useSwapContext } from '../contexts/Swap.js' export const useSwap = () => { - const { - fromCoin, - toCoin, - amount, - nonRecentAmount, - recentInput, - isSwapReady, - isSwapQuotePending, - hasInsufficientFunds, - isErrorSwapQuote, - isTxnPending, - isErrorTxn, - setFromCoin, - setToCoin, - setAmount, - switchCoinOrder, - onSubmitSwap, - resetSwapStates - } = useSwapContext() + const { lifiChains, lifiTokens } = useSwapContext() return { - fromCoin, - toCoin, - amount, - nonRecentAmount, - recentInput, - isSwapReady, - isSwapQuotePending, - hasInsufficientFunds, - isErrorSwapQuote, - isTxnPending, - isErrorTxn, - setFromCoin, - setToCoin, - setAmount, - switchCoinOrder, - onSubmitSwap, - resetSwapStates + lifiChains, + lifiTokens } } diff --git a/packages/wallet-widget/src/hooks/useValueRegistry.ts b/packages/wallet-widget/src/hooks/useValueRegistry.ts new file mode 100644 index 000000000..2b92b4475 --- /dev/null +++ b/packages/wallet-widget/src/hooks/useValueRegistry.ts @@ -0,0 +1,7 @@ +import { useValueRegistryContext } from '../contexts/ValueRegistry.js' + +export const useValueRegistry = () => { + const { valueRegistryMap, totalValue } = useValueRegistryContext() + + return { valueRegistryMap, totalValue } +} diff --git a/packages/wallet-widget/src/utils/formatBalance.ts b/packages/wallet-widget/src/utils/formatBalance.ts index b3f66cf50..aa9923e8a 100644 --- a/packages/wallet-widget/src/utils/formatBalance.ts +++ b/packages/wallet-widget/src/utils/formatBalance.ts @@ -5,7 +5,7 @@ import type { TokenBalance } from '@0xsequence/indexer' import { formatUnits, type Chain } from 'viem' import { zeroAddress } from 'viem' -import type { TokenBalanceWithPrice } from './tokens.js' +import type { TokenBalanceWithDetails } from './tokens.js' //TODO: rename these and maybe do a refactor @@ -30,7 +30,7 @@ interface TokenInfo { } export const formatTokenInfo = ( - balance: TokenBalanceWithPrice | undefined, + balance: TokenBalanceWithDetails | undefined, fiatSign: string, chains: readonly [Chain, ...Chain[]] ): TokenInfo => { @@ -49,6 +49,7 @@ export const formatTokenInfo = ( const bal = formatUnits(BigInt(balance?.balance || 0), decimals || 18) const displayBalance = formatDisplay(bal) const symbol = isNativeToken ? nativeTokenInfo.symbol : balance?.contractInfo?.symbol + const fiatBalance = (balance?.price?.value || 0) * Number(bal) return { isNativeToken, @@ -57,7 +58,7 @@ export const formatTokenInfo = ( name: selectedCoinName, symbol: selectedCoinSymbol, displayBalance: `${displayBalance} ${symbol}`, - fiatBalance: `${fiatSign}${(balance.price.value * Number(bal)).toFixed(2)}` + fiatBalance: `${fiatSign}${fiatBalance.toFixed(2)}` } } diff --git a/packages/wallet-widget/src/utils/tokens.ts b/packages/wallet-widget/src/utils/tokens.ts index 5198c8e6e..210e98b18 100644 --- a/packages/wallet-widget/src/utils/tokens.ts +++ b/packages/wallet-widget/src/utils/tokens.ts @@ -4,8 +4,9 @@ import type { GetTransactionHistoryReturn, TokenBalance, Transaction } from '@0x import type { InfiniteData } from '@tanstack/react-query' import { formatUnits, zeroAddress } from 'viem' -export interface TokenBalanceWithPrice extends TokenBalance { - price: Price +export interface TokenBalanceWithDetails extends TokenBalance { + price?: Price + _type?: 'coin' | 'collectible' | 'collection' } export const getPercentageColor = (value: number) => { diff --git a/packages/wallet-widget/src/views/Buy/index.tsx b/packages/wallet-widget/src/views/Buy/index.tsx new file mode 100644 index 000000000..0b769fd62 --- /dev/null +++ b/packages/wallet-widget/src/views/Buy/index.tsx @@ -0,0 +1,32 @@ +import { useAddFundsModal } from '@0xsequence/checkout' +import { useWallets } from '@0xsequence/connect' +import { AddIcon, Text } from '@0xsequence/design-system' +import { useAccount } from 'wagmi' + +import { WalletSelect } from '../../components/Select/WalletSelect.js' + +export const Buy = () => { + const { address } = useAccount() + const { setActiveWallet } = useWallets() + const { triggerAddFunds } = useAddFundsModal() + + const onClickWallet = (address: string) => { + setActiveWallet(address) + } + + const onClickAddFunds = () => { + triggerAddFunds({ walletAddress: address || '' }) + } + + return ( +
+ +
+ + + Add Funds to Selected Wallet + +
+
+ ) +} diff --git a/packages/wallet-widget/src/views/CoinDetails/Skeleton.tsx b/packages/wallet-widget/src/views/CoinDetails/Skeleton.tsx index d25b33538..475d26103 100644 --- a/packages/wallet-widget/src/views/CoinDetails/Skeleton.tsx +++ b/packages/wallet-widget/src/views/CoinDetails/Skeleton.tsx @@ -1,8 +1,7 @@ -import { Button, SendIcon, Skeleton, Text } from '@0xsequence/design-system' +import { Button, SendIcon, Skeleton, SwapIcon, Text } from '@0xsequence/design-system' import { NetworkBadge } from '../../components/NetworkBadge.js' import { TransactionHistorySkeleton } from '../../components/TransactionHistoryList/TransactionHistorySkeleton.js' -import { HEADER_HEIGHT } from '../../constants/index.js' interface CoinDetailsSkeletonProps { chainId: number @@ -11,8 +10,8 @@ interface CoinDetailsSkeletonProps { export const CoinDetailsSkeleton = ({ chainId, isReadOnly }: CoinDetailsSkeletonProps) => { return ( -
-
+
+
@@ -28,14 +27,22 @@ export const CoinDetailsSkeleton = ({ chainId, isReadOnly }: CoinDetailsSkeleton
{!isReadOnly && ( -
)}
diff --git a/packages/wallet-widget/src/views/CoinDetails/index.tsx b/packages/wallet-widget/src/views/CoinDetails/index.tsx index 159308fc5..a4ba6a739 100644 --- a/packages/wallet-widget/src/views/CoinDetails/index.tsx +++ b/packages/wallet-widget/src/views/CoinDetails/index.tsx @@ -6,21 +6,15 @@ import { formatUnits, zeroAddress } from 'viem' import { useConfig } from 'wagmi' import { InfiniteScroll } from '../../components/InfiniteScroll.js' +import type { TokenInfo } from '../../components/NavigationHeader/index.js' import { NetworkBadge } from '../../components/NetworkBadge.js' import { TransactionHistoryList } from '../../components/TransactionHistoryList/index.js' -import { HEADER_HEIGHT } from '../../constants/index.js' import { useNavigation, useSettings } from '../../hooks/index.js' import { computeBalanceFiat, flattenPaginatedTransactionHistory } from '../../utils/index.js' import { CoinDetailsSkeleton } from './Skeleton.js' -export interface CoinDetailsProps { - contractAddress: string - chainId: number - accountAddress: string -} - -export const CoinDetails = ({ contractAddress, chainId, accountAddress }: CoinDetailsProps) => { +export const CoinDetails = ({ contractAddress, chainId, accountAddress = '' }: TokenInfo) => { const { chains } = useConfig() const { setNavigation } = useNavigation() const { fiatCurrency } = useSettings() @@ -104,8 +98,8 @@ export const CoinDetails = ({ contractAddress, chainId, accountAddress }: CoinDe }) } return ( -
-
+
+
@@ -124,8 +118,18 @@ export const CoinDetails = ({ contractAddress, chainId, accountAddress }: CoinDe
{!isReadOnly && (
-
)}
diff --git a/packages/wallet-widget/src/views/CollectibleDetails/InfoBadge.tsx b/packages/wallet-widget/src/views/CollectibleDetails/InfoBadge.tsx new file mode 100644 index 000000000..3afbe4d5c --- /dev/null +++ b/packages/wallet-widget/src/views/CollectibleDetails/InfoBadge.tsx @@ -0,0 +1,19 @@ +import { cn, Text } from '@0xsequence/design-system' +import type { ReactNode } from 'react' + +export const InfoBadge = ({ leftIcon, label, onClick }: { leftIcon?: ReactNode; label: string; onClick?: () => void }) => { + return ( +
+ {leftIcon} + + {label} + +
+ ) +} diff --git a/packages/wallet-widget/src/views/CollectibleDetails/PropertiesBadge.tsx b/packages/wallet-widget/src/views/CollectibleDetails/PropertiesBadge.tsx new file mode 100644 index 000000000..1a8db3b0b --- /dev/null +++ b/packages/wallet-widget/src/views/CollectibleDetails/PropertiesBadge.tsx @@ -0,0 +1,19 @@ +import { Text } from '@0xsequence/design-system' + +export const PropertiesBadge = ({ name, value }: { name: string; value: string }) => { + return ( +
+ + Property + +
+ + {name} + + + {value} + +
+
+ ) +} diff --git a/packages/wallet-widget/src/views/CollectibleDetails/Skeleton.tsx b/packages/wallet-widget/src/views/CollectibleDetails/Skeleton.tsx index dd2d077fe..4d089d221 100644 --- a/packages/wallet-widget/src/views/CollectibleDetails/Skeleton.tsx +++ b/packages/wallet-widget/src/views/CollectibleDetails/Skeleton.tsx @@ -1,7 +1,4 @@ -import { Button, SendIcon, Skeleton, Text } from '@0xsequence/design-system' - -import { TransactionHistorySkeleton } from '../../components/TransactionHistoryList/TransactionHistorySkeleton.js' -import { HEADER_HEIGHT } from '../../constants/index.js' +import { ArrowUpIcon, Button, Divider, ExternalLinkIcon, Skeleton } from '@0xsequence/design-system' interface CollectibleDetailsSkeletonProps { isReadOnly: boolean @@ -9,41 +6,34 @@ interface CollectibleDetailsSkeletonProps { export const CollectibleDetailsSkeleton = ({ isReadOnly }: CollectibleDetailsSkeletonProps) => { return ( -
-
-
- - -
-
- -
-
- {/* balance */} -
- - Balance - -
- - -
-
- {!isReadOnly && ( -
-
- - This week - - +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + +
) diff --git a/packages/wallet-widget/src/views/CollectibleDetails/index.tsx b/packages/wallet-widget/src/views/CollectibleDetails/index.tsx index eabc35a81..bcd30d834 100644 --- a/packages/wallet-widget/src/views/CollectibleDetails/index.tsx +++ b/packages/wallet-widget/src/views/CollectibleDetails/index.tsx @@ -1,75 +1,59 @@ -import { formatDisplay, useWallets } from '@0xsequence/connect' -import { Button, Image, NetworkImage, SendIcon, Text } from '@0xsequence/design-system' +import { formatDisplay, truncateAtIndex } from '@0xsequence/connect' import { - useGetCollectiblePrices, - useGetExchangeRate, - useGetSingleTokenBalance, - useGetTransactionHistory -} from '@0xsequence/hooks' -import { useEffect } from 'react' -import { formatUnits } from 'viem' + ArrowUpIcon, + Button, + Divider, + ExternalLinkIcon, + GradientAvatar, + Image, + NetworkImage, + Skeleton, + Text +} from '@0xsequence/design-system' +import { useGetSingleTokenBalance } from '@0xsequence/hooks' +import { findSupportedNetwork } from '@0xsequence/network' +import * as PopoverPrimitive from '@radix-ui/react-popover' +import { useEffect, useRef, useState } from 'react' +import { formatUnits, getAddress } from 'viem' import { useConfig } from 'wagmi' -import { CollectibleTileImage } from '../../components/CollectibleTileImage.js' -import { InfiniteScroll } from '../../components/InfiniteScroll.js' -import { TransactionHistoryList } from '../../components/TransactionHistoryList/index.js' -import { HEADER_HEIGHT } from '../../constants/index.js' +import type { TokenInfo } from '../../components/NavigationHeader/index.js' +import { TokenTileImage } from '../../components/TokenTileImage.js' import { useNavigation, useSettings } from '../../hooks/index.js' -import { computeBalanceFiat, flattenPaginatedTransactionHistory } from '../../utils/index.js' +import { InfoBadge } from './InfoBadge.js' +import { PropertiesBadge } from './PropertiesBadge.js' import { CollectibleDetailsSkeleton } from './Skeleton.js' -export interface CollectibleDetailsProps { - contractAddress: string - chainId: number - tokenId: string - accountAddress: string -} -export const CollectibleDetails = ({ contractAddress, chainId, tokenId, accountAddress }: CollectibleDetailsProps) => { +export const CollectibleDetails = ({ contractAddress, chainId, tokenId, accountAddress }: TokenInfo) => { const { chains } = useConfig() - const { setActiveWallet } = useWallets() - const { fiatCurrency } = useSettings() const { setNavigation } = useNavigation() + const network = findSupportedNetwork(chainId) + const { hideUnlistedTokens } = useSettings() - useEffect(() => { - setActiveWallet(accountAddress || '') - }, [accountAddress]) + const triggerRef = useRef(null) + const [triggerWidth, setTriggerWidth] = useState(0) - const isReadOnly = !chains.map(chain => chain.id).includes(chainId) + const [isExternalPopoverOpen, setIsExternalPopoverOpen] = useState(false) + const [foundMarketplaceURL, setFoundMarketplaceURL] = useState(null) - const { - data: dataTransactionHistory, - isLoading: isLoadingTransactionHistory, - fetchNextPage, - hasNextPage, - isFetchingNextPage - } = useGetTransactionHistory({ - chainId, - accountAddresses: [accountAddress || ''], - contractAddresses: [contractAddress], - tokenId - }) + useEffect(() => { + if (triggerRef.current) { + setTriggerWidth(triggerRef.current.offsetWidth) + } + }, [isExternalPopoverOpen]) - const transactionHistory = flattenPaginatedTransactionHistory(dataTransactionHistory) + const isReadOnly = !chains.map(chain => chain.id).includes(chainId) const { data: tokenBalance, isLoading: isLoadingCollectibleBalance } = useGetSingleTokenBalance({ chainId, contractAddress, accountAddress: accountAddress || '', - tokenId + tokenId, + hideUnlistedTokens: hideUnlistedTokens }) - const { data: dataCollectiblePrices, isLoading: isLoadingCollectiblePrices } = useGetCollectiblePrices([ - { - chainId, - contractAddress, - tokenId - } - ]) - - const { data: conversionRate = 1, isLoading: isLoadingConversionRate } = useGetExchangeRate(fiatCurrency.symbol) - - const isLoading = isLoadingCollectibleBalance || isLoadingCollectiblePrices || isLoadingConversionRate + const isLoading = isLoadingCollectibleBalance if (isLoading) { return @@ -81,11 +65,18 @@ export const CollectibleDetails = ({ contractAddress, chainId, tokenId, accountA params: { chainId, contractAddress, - tokenId + tokenId: tokenId || '' } }) } + const onClickOpenScan = () => { + // window.open(`${network?.blockExplorer?.rootUrl}token/${contractAddress}?a=${tokenId}`, '_blank') + window.open(`${network?.blockExplorer?.rootUrl}nft/${contractAddress}/${tokenId}`, '_blank') + } + + const onClickOpenMarketplace = () => {} + const collectionLogo = tokenBalance?.contractInfo?.logoURI const collectionName = tokenBalance?.contractInfo?.name || 'Unknown Collection' @@ -94,88 +85,144 @@ export const CollectibleDetails = ({ contractAddress, chainId, tokenId, accountA const balance = formatUnits(BigInt(rawBalance), decimals) const formattedBalance = formatDisplay(Number(balance)) - const valueFiat = tokenBalance - ? computeBalanceFiat({ - balance: tokenBalance, - prices: dataCollectiblePrices || [], - conversionRate, - decimals: decimals - }) - : '0' + const onClickCollection = () => { + setNavigation({ + location: 'collection-details', + params: { + chainId, + contractAddress + } + }) + } return ( -
-
-
-
- {collectionLogo && ( - collection logo +
+ +
+
-
- - {tokenBalance?.tokenMetadata?.name || 'Unknown Collectible'} - - - {`#${tokenId}`} - -
+
-
- + + {tokenBalance?.tokenMetadata?.name || 'Unknown Collectible'} + +
+ + Network + + } + label={chains.find(chain => chain.id === chainId)?.name || 'Unknown Network'} + />
-
- {/* balance */} -
- - Balance - -
- - {formattedBalance} - - {dataCollectiblePrices && dataCollectiblePrices[0].price?.value && ( - {`${fiatCurrency.symbol} ${valueFiat}`} - )} -
-
- {!isReadOnly && ( -
) diff --git a/packages/wallet-widget/src/views/CollectionDetails/index.tsx b/packages/wallet-widget/src/views/CollectionDetails/index.tsx new file mode 100644 index 000000000..ccaca2171 --- /dev/null +++ b/packages/wallet-widget/src/views/CollectionDetails/index.tsx @@ -0,0 +1,89 @@ +import { ContractVerificationStatus, useWallets } from '@0xsequence/connect' +import { Divider, TabsContent, TabsPrimitive, Text } from '@0xsequence/design-system' +import { useGetTokenBalancesByContract } from '@0xsequence/hooks' +import type { TokenBalance } from '@0xsequence/indexer' +import { useState } from 'react' + +import type { TokenInfo } from '../../components/NavigationHeader/index.js' +import { NoResults } from '../../components/NoResults.js' +import { CollectiblesTab } from '../../components/SearchLists/CollectiblesList/CollectiblesTab.js' +import { useNavigation, useSettings } from '../../hooks/index.js' + +export const CollectionDetails = ({ contractAddress, chainId }: TokenInfo) => { + const { setNavigation } = useNavigation() + const { wallets } = useWallets() + const { hideUnlistedTokens } = useSettings() + + const [selectedTab, setSelectedTab] = useState<'collectibles' | 'explore'>('collectibles') + + const { + data: collectibles, + isLoading, + fetchNextPage, + hasNextPage, + isFetchingNextPage + } = useGetTokenBalancesByContract({ + chainIds: [chainId], + filter: { + accountAddresses: wallets.map(wallet => wallet.address), + contractAddresses: [contractAddress], + contractStatus: hideUnlistedTokens ? ContractVerificationStatus.VERIFIED : ContractVerificationStatus.ALL + } + }) + + const onClickCollectible = (collectible: TokenBalance) => { + setNavigation({ + location: 'collectible-details', + params: { + contractAddress: collectible.contractAddress, + chainId: collectible.chainId, + tokenId: collectible.tokenID || '', + accountAddress: collectible.accountAddress + } + }) + } + + return ( +
+ setSelectedTab(value as 'collectibles' | 'explore')} + > +
+ + + + My Collectibles + + {selectedTab === 'collectibles' &&
} + + + + Explore + + {selectedTab === 'explore' &&
} + + + +
+ +
+ + page.balances) || []} + fetchMoreCollectibleBalances={fetchNextPage} + hasMoreCollectibleBalances={hasNextPage} + isFetchingMoreCollectibleBalances={isFetchingNextPage} + isFetchingInitialBalances={isLoading} + onTokenClick={onClickCollectible} + /> + + + + +
+ +
+ ) +} diff --git a/packages/wallet-widget/src/views/History.tsx b/packages/wallet-widget/src/views/History.tsx deleted file mode 100644 index 4d53d6dca..000000000 --- a/packages/wallet-widget/src/views/History.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { getNativeTokenInfoByChainId } from '@0xsequence/connect' -import { compareAddress, SearchIcon, TextInput } from '@0xsequence/design-system' -import { useGetTransactionHistorySummary } from '@0xsequence/hooks' -import type { Transaction } from '@0xsequence/indexer' -import Fuse from 'fuse.js' -import { useObservable } from 'micro-observables' -import { useMemo, useState } from 'react' -import { zeroAddress } from 'viem' -import { useConfig } from 'wagmi' - -import { FilterButton } from '../components/Filter/FilterButton.js' -import { TransactionHistoryList } from '../components/TransactionHistoryList/index.js' -import { useSettings } from '../hooks/index.js' - -export const History = () => { - const { chains } = useConfig() - const { selectedNetworksObservable, selectedWalletsObservable } = useSettings() - - const selectedNetworks = useObservable(selectedNetworksObservable) - const selectedWallets = useObservable(selectedWalletsObservable) - - const [search, setSearch] = useState('') - - const { data: transactionHistory = [], isLoading: isLoadingTransactionHistory } = useGetTransactionHistorySummary({ - accountAddresses: selectedWallets.map(wallet => wallet.address), - chainIds: selectedNetworks - }) - - const fuseOptions = { - threshold: 0.1, - ignoreLocation: true, - keys: [ - { - name: 'contractName', - getFn: (transaction: Transaction) => { - return transaction.transfers?.map(transfer => transfer.contractInfo?.name).join(', ') || '' - } - }, - { - name: 'tokenSymbol', - getFn: (transaction: Transaction) => { - const hasNativeToken = transaction.transfers?.some(transfer => - compareAddress(transfer.contractInfo?.address, zeroAddress) - ) - if (hasNativeToken) { - const nativeTokenInfo = getNativeTokenInfoByChainId(transaction.chainId, chains) - return nativeTokenInfo.symbol - } - return transaction.transfers?.map(transfer => transfer.contractInfo?.symbol).join(', ') || '' - } - }, - { - name: 'collectibleName', - getFn: (transaction: Transaction) => { - return ( - transaction.transfers - ?.map(transfer => { - return Object.values(transfer.tokenMetadata || {}) - .map(tokenMetadata => { - return tokenMetadata?.name - }) - .join(', ') - }) - .join(', ') || '' - ) - } - }, - { - name: 'date', - getFn: (transaction: Transaction) => { - const date = new Date(transaction.timestamp) - const day = date.getDate() - const month = date.toLocaleString('en-US', { month: 'long' }) - const year = date.getFullYear() - return ` - ${day} ${month} ${year} - ${day} ${year} ${month} - ${month} ${day} ${year} - ${month} ${year} ${day} - ${year} ${day} ${month} - ${year} ${month} ${day} - ` - }, - customSearchFn: (query: string, options: { keys: string[] }) => { - const queryParts = query.toLowerCase().split(/\s+/) - const dayQuery = queryParts.find(part => /^\d{1,2}$/.test(part)) - const monthQuery = queryParts.find(part => /^[a-zA-Z]+$/.test(part)) - const yearQuery = queryParts.find(part => /^\d{4}$/.test(part)) - - console.log(dayQuery, monthQuery, yearQuery) - - return options.keys.some(key => { - const keyParts = key.toLowerCase().split(/\s+/) - let keyDay = '', - keyMonth = '', - keyYear = '' - - keyParts.forEach(part => { - if (/^\d{1,2}$/.test(part)) { - keyDay = part - } else if (/^[a-zA-Z]+$/.test(part)) { - keyMonth = part - } else if (/^\d{4}$/.test(part)) { - keyYear = part - } - }) - - return ( - (!dayQuery || keyDay === dayQuery) && - (!monthQuery || keyMonth === monthQuery) && - (!yearQuery || keyYear === yearQuery) - ) - }) - } - } - ] - } - - const fuse = useMemo(() => { - return new Fuse(transactionHistory, fuseOptions) - }, [transactionHistory]) - - const searchResults = useMemo(() => { - if (!search.trimStart()) { - return [] - } - return fuse.search(search).map(result => result.item) - }, [search, fuse]) - - return ( -
-
-
- setSearch(ev.target.value)} - placeholder="Search your wallet" - data-1p-ignore - /> -
- -
- -
- ) -} diff --git a/packages/wallet-widget/src/views/Home/OperationButtonTemplate.tsx b/packages/wallet-widget/src/views/Home/OperationButtonTemplate.tsx deleted file mode 100644 index e431a7f53..000000000 --- a/packages/wallet-widget/src/views/Home/OperationButtonTemplate.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { cardVariants, cn, Text, type IconProps } from '@0xsequence/design-system' -import type { ComponentType } from 'react' - -export const OperationButtonTemplate = ({ - label, - onClick, - icon: Icon -}: { - label: string - onClick: () => void - icon: ComponentType -}) => { - return ( -
- {Icon && } - - {label} - -
- ) -} diff --git a/packages/wallet-widget/src/views/Home/index.tsx b/packages/wallet-widget/src/views/Home/index.tsx index eb27a44a0..8ff3da318 100644 --- a/packages/wallet-widget/src/views/Home/index.tsx +++ b/packages/wallet-widget/src/views/Home/index.tsx @@ -1,384 +1,78 @@ -import { useAddFundsModal } from '@0xsequence/checkout' -import { compareAddress, formatAddress, getNativeTokenInfoByChainId, useOpenConnectModal, useWallets } from '@0xsequence/connect' -import { - AddIcon, - ArrowUpIcon, - Button, - ChevronUpDownIcon, - EllipsisIcon, - ScanIcon, - Skeleton, - SwapIcon, - Text -} from '@0xsequence/design-system' -import { useGetCoinPrices, useGetExchangeRate } from '@0xsequence/hooks' -import { ethers } from 'ethers' -import { useObservable } from 'micro-observables' -import { AnimatePresence } from 'motion/react' -import { useEffect, useMemo, useState } from 'react' -import { useAccount, useConfig } from 'wagmi' +import { truncateAtIndex, useSocialLink, useWallets } from '@0xsequence/connect' +import { Text } from '@0xsequence/design-system' +import { useAccount } from 'wagmi' import { CopyButton } from '../../components/CopyButton.js' -import { WalletsFilter } from '../../components/Filter/WalletsFilter.js' -import { StackedIconTag } from '../../components/IconWrappers/StackedIconTag.js' -import { ListCardNav } from '../../components/ListCard/ListCardNav.js' -import { ListCardNavTable } from '../../components/ListCardTable/ListCardNavTable.js' -import { SelectWalletRow } from '../../components/Select/SelectWalletRow.js' -import { SlideupDrawer } from '../../components/Select/SlideupDrawer.js' +import { GeneralList } from '../../components/SearchLists/index.js' import { WalletAccountGradient } from '../../components/WalletAccountGradient.js' -import { useFiatWalletsMap, useGetAllTokensDetails, useNavigation, useSettings } from '../../hooks/index.js' -import { computeBalanceFiat } from '../../utils/index.js' - -import { OperationButtonTemplate } from './OperationButtonTemplate.js' +import { useSettings, useValueRegistry } from '../../hooks/index.js' +import { useShared } from '../../hooks/useShared.js' export const Home = () => { - const { setNavigation } = useNavigation() - const { selectedWalletsObservable, selectedNetworks, hideUnlistedTokens, fiatCurrency, selectedCollections } = useSettings() - const { fiatWalletsMap } = useFiatWalletsMap() - const { connector } = useAccount() - - const selectedWallets = useObservable(selectedWalletsObservable) - const { chains } = useConfig() + const { isGuest, signInDisplay } = useShared() + const { wallets: allWallets } = useWallets() + const { fiatCurrency } = useSettings() + const { totalValue } = useValueRegistry() + const { setIsSocialLinkOpen } = useSocialLink() const { address: accountAddress } = useAccount() - const { wallets, setActiveWallet } = useWallets() - - const { setOpenConnectModal } = useOpenConnectModal() - const { triggerAddFunds } = useAddFundsModal() - const [accountSelectorModalOpen, setAccountSelectorModalOpen] = useState(false) - const [walletFilterOpen, setWalletFilterOpen] = useState(false) - - const [signInDisplay, setSignInDisplay] = useState('') - - useEffect(() => { - const fetchSignInDisplay = async () => { - const sequenceWaas = (await connector?.sequenceWaas) as { - listAccounts: () => Promise<{ accounts: { email: string; type: string }[] }> - } - - if (sequenceWaas) { - const sequenceWaasAccounts = await sequenceWaas.listAccounts() - const waasEmail = sequenceWaasAccounts.accounts.find(account => account.type === 'OIDC')?.email - let backupEmail = '' - if (sequenceWaasAccounts.accounts.length > 0) { - backupEmail = sequenceWaasAccounts.accounts[0].email - } - setSignInDisplay(waasEmail || backupEmail) - } else { - setSignInDisplay(connector?.name || '') - } - } - fetchSignInDisplay() - }, [connector]) - - const { data: tokenBalancesData, isLoading: isLoadingTokenBalances } = useGetAllTokensDetails({ - accountAddresses: [accountAddress || ''], - chainIds: selectedNetworks, - contractWhitelist: selectedCollections.map(collection => collection.contractAddress), - hideUnlistedTokens - }) - - const coinBalancesUnordered = - tokenBalancesData?.filter(b => b.contractType === 'ERC20' || compareAddress(b.contractAddress, ethers.ZeroAddress)) || [] - - const isSingleCollectionSelected = selectedCollections.length === 1 - - const collectibleBalancesUnordered = - tokenBalancesData?.filter(token => { - if (token.contractType !== 'ERC721' && token.contractType !== 'ERC1155') { - return false - } - if (isSingleCollectionSelected) { - return token.chainId === selectedCollections[0].chainId - } - return true - }) || [] - - const { data: coinPrices = [], isLoading: isLoadingCoinPrices } = useGetCoinPrices( - coinBalancesUnordered.map(token => ({ - chainId: token.chainId, - contractAddress: token.contractAddress - })) - ) - - const { data: conversionRate, isLoading: isLoadingConversionRate } = useGetExchangeRate(fiatCurrency.symbol) - - const isLoading = isLoadingTokenBalances || isLoadingCoinPrices || isLoadingConversionRate - - const totalFiatValue = fiatWalletsMap - .reduce((acc, wallet) => { - if (selectedWallets.some(selectedWallet => selectedWallet.address === wallet.accountAddress)) { - const walletFiatValue = Number(wallet.fiatValue) - return acc + walletFiatValue - } - return acc - }, 0) - .toFixed(2) - - const coinBalances = coinBalancesUnordered.sort((a, b) => { - const isHigherFiat = - Number( - computeBalanceFiat({ - balance: b, - prices: coinPrices, - conversionRate: conversionRate || 1, - decimals: b.contractInfo?.decimals || 18 - }) - ) - - Number( - computeBalanceFiat({ - balance: a, - prices: coinPrices, - conversionRate: conversionRate || 1, - decimals: a.contractInfo?.decimals || 18 - }) - ) - return isHigherFiat - }) - const collectibleBalances = collectibleBalancesUnordered.sort((a, b) => { - return Number(b.balance) - Number(a.balance) - }) - - const coinBalancesIconSet = new Set() - const coinBalancesIcons = useMemo( - () => - coinBalances - .map(coin => { - const isNativeToken = compareAddress(coin.contractAddress, ethers.ZeroAddress) - const nativeTokenInfo = getNativeTokenInfoByChainId(coin.chainId, chains) - const logoURI = isNativeToken ? nativeTokenInfo.logoURI : coin.contractInfo?.logoURI - const tokenName = isNativeToken ? nativeTokenInfo.name : coin.contractInfo?.name - - if (coinBalancesIconSet.has(tokenName) || !logoURI) { - return - } - - coinBalancesIconSet.add(tokenName) - if (coinBalancesIconSet.size <= 3) { - return logoURI - } - }) - .filter(Boolean) as string[], - [coinBalances, selectedWallets, selectedNetworks, hideUnlistedTokens, selectedCollections] - ) - - const collectibleBalancesIconSet = new Set() - const collectibleBalancesIcons = useMemo( - () => - collectibleBalances - .map(collectible => { - const logoURI = collectible.tokenMetadata?.image - - if (collectibleBalancesIconSet.has(logoURI) || !logoURI) { - return - } - - collectibleBalancesIconSet.add(logoURI) - if (collectibleBalancesIconSet.size <= 3) { - return logoURI - } - }) - .filter(Boolean) as string[], - [collectibleBalances, selectedWallets, selectedNetworks, hideUnlistedTokens, selectedCollections] - ) - - const onClickAccountSelector = () => { - setAccountSelectorModalOpen(true) - } - const handleAddNewWallet = () => { - setAccountSelectorModalOpen(false) - setOpenConnectModal(true) - } - const onClickSend = () => { - setNavigation({ - location: 'send-general' - }) - } - const onClickSwap = () => { - setNavigation({ - location: 'swap' - }) - } - const onClickReceive = () => { - setNavigation({ - location: 'receive' - }) - } - const onClickAddFunds = () => { - triggerAddFunds({ walletAddress: accountAddress || '' }) - } - const onClickWalletSelector = () => { - setWalletFilterOpen(true) + const onClickLinkGuestAccount = () => { + setIsSocialLinkOpen(true) } - const onClickTokens = () => { - setNavigation({ - location: 'search-tokens' - }) - } - const onClickCollectibles = () => { - setNavigation({ - location: 'search-collectibles' - }) - } - const onClickTransactions = () => { - setNavigation({ - location: 'history' - }) - } - const onClickSettings = () => { - setNavigation({ - location: 'settings' - }) - } - - const homeNavTableItems = [ - 0 ? ( -
- - {fiatCurrency.sign} - {isLoading ? : `${totalFiatValue}`} - - - {coinBalances.length} - - } - /> -
- ) : ( - - No Tokens - - ) - } - shape="square" - > - - Tokens - -
, - 0 ? ( - - {collectibleBalances.length} - - } - shape="square" - /> - ) : ( - - No Collectibles - - ) - } - shape="square" - > - - Collectibles - - - ] return ( -
-
- -
-
- - {formatAddress(accountAddress || '')} +
+
+
+ {allWallets.length > 1 ? ( + wallet.address)} /> + ) : ( +
+ wallet.address)} /> +
+
+ + {truncateAtIndex(accountAddress || '', 8)} + + +
+ {signInDisplay && ( + + {signInDisplay} + + )} +
+
+ )} + +
+ + Balance - -
- {signInDisplay && ( - - {signInDisplay} + + {fiatCurrency.symbol} {fiatCurrency.sign} + {totalValue} - )} +
- -
-
- - - - -
-
- - <> - - Items - - wallet.address)} - label={ -
- - {`${selectedWallets.length} Wallet${selectedWallets.length === 1 ? '' : 's'}`} - - -
- } - isAccount - enabled - onClick={onClickWalletSelector} - /> - -
- - - Transactions - - - - - Settings + {isGuest && ( + { + onClickLinkGuestAccount() + }} + > + Click here to link your guest account - + )}
- - {accountSelectorModalOpen && ( - setAccountSelectorModalOpen(false)} - label="Select main wallet" - buttonLabel="+ Add new wallet" - handleButtonPress={handleAddNewWallet} - dragHandleWidth={74} - > -
- {wallets.map((wallet, index) => ( - setAccountSelectorModalOpen(false)} - onClick={setActiveWallet} - /> - ))} -
-
- )} - {walletFilterOpen && ( - setWalletFilterOpen(false)} label="Filter items by wallet"> - - - )} -
+
+ +
) } diff --git a/packages/wallet-widget/src/views/Receive.tsx b/packages/wallet-widget/src/views/Receive.tsx deleted file mode 100644 index f038fa565..000000000 --- a/packages/wallet-widget/src/views/Receive.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { getNetwork } from '@0xsequence/connect' -import { Button, CopyIcon, Image, ShareIcon, Text } from '@0xsequence/design-system' -import { useClipboard } from '@0xsequence/hooks' -import { QRCodeCanvas } from 'qrcode.react' -import { useAccount } from 'wagmi' - -import { NetworkSelect } from '../components/Select/NetworkSelect.js' -import { HEADER_HEIGHT_WITH_LABEL } from '../constants/index.js' - -const isVowel = (char: string) => ['a', 'e', 'i', 'o', 'u'].includes(char.toLowerCase()) - -export const Receive = () => { - const { address, chain } = useAccount() - const [isCopied, setCopied] = useClipboard({ timeout: 4000 }) - - const networkInfo = getNetwork(chain?.id || 1) - const networkName = networkInfo.title || networkInfo.name - - const onClickShare = () => { - if (typeof window !== 'undefined') { - window.open(`https://twitter.com/intent/tweet?text=Here%20is%20my%20address%20${address}`) - } - } - - return ( -
-
- -
- -
-
-
- - My Wallet - - icon -
-
- - {address} - -
-
-
-
-
- - {`This is a${isVowel(networkName[0]) ? 'n' : ''} ${networkName} address. Please only send assets on the ${networkName} network.`} - -
-
-
- ) -} diff --git a/packages/wallet-widget/src/views/Receive/index.tsx b/packages/wallet-widget/src/views/Receive/index.tsx new file mode 100644 index 000000000..bf2c6e6f6 --- /dev/null +++ b/packages/wallet-widget/src/views/Receive/index.tsx @@ -0,0 +1,56 @@ +import { useWallets } from '@0xsequence/connect' +import { Button, CopyIcon, ShareIcon, Text } from '@0xsequence/design-system' +import { useClipboard } from '@0xsequence/hooks' +import { QRCodeCanvas } from 'qrcode.react' +import { useAccount } from 'wagmi' + +import { WalletSelect } from '../../components/Select/WalletSelect.js' + +export const Receive = () => { + const { address } = useAccount() + const { setActiveWallet } = useWallets() + const [isCopied, setCopied] = useClipboard({ timeout: 4000 }) + + const onClickWallet = (address: string) => { + setActiveWallet(address) + } + + const onClickShare = () => { + if (typeof window !== 'undefined') { + window.open(`https://twitter.com/intent/tweet?text=Here%20is%20my%20address%20${address}`) + } + } + + return ( +
+ +
+ +
+
+
+ + My Wallet + +
+
+ + {address} + +
+
+
+
+
+ ) +} diff --git a/packages/wallet-widget/src/views/Search/SearchCollectibles.tsx b/packages/wallet-widget/src/views/Search/SearchCollectibles.tsx deleted file mode 100644 index 7b0cda7fd..000000000 --- a/packages/wallet-widget/src/views/Search/SearchCollectibles.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useGetContractInfo } from '@0xsequence/hooks' -import { useObservable } from 'micro-observables' -import { useEffect } from 'react' - -import { CollectiblesList } from '../../components/SearchLists/CollectiblesList.js' -import { useGetAllTokensDetails, useNavigation, useSettings } from '../../hooks/index.js' -import type { TokenBalanceWithPrice } from '../../utils/index.js' - -export const SearchCollectibles = ({ - contractAddress, - chainId -}: { - contractAddress: string | undefined - chainId: number | undefined -}) => { - const { - selectedWalletsObservable, - selectedNetworksObservable, - selectedCollectionsObservable, - hideUnlistedTokens, - setSelectedCollections - } = useSettings() - const { setNavigation } = useNavigation() - - // Only fetch contract info if contract address and chain id are provided - const { data: contractInfo } = useGetContractInfo( - { - chainID: String(chainId), - contractAddress: contractAddress || '' - }, - { disabled: !contractAddress || !chainId } - ) - - useEffect(() => { - if (contractAddress && chainId && contractInfo) { - setSelectedCollections([ - { - contractAddress, - chainId: Number(chainId), - logoURI: contractInfo.logoURI, - name: contractInfo.name - } - ]) - } else { - setSelectedCollections([]) - } - }, [contractInfo, contractAddress, chainId]) - - const selectedWallets = useObservable(selectedWalletsObservable) - const selectedNetworks = useObservable(selectedNetworksObservable) - const selectedCollections = useObservable(selectedCollectionsObservable) - - const { data: tokenBalancesData = [], isLoading } = useGetAllTokensDetails({ - accountAddresses: selectedWallets.map(wallet => wallet.address), - chainIds: selectedNetworks, - contractWhitelist: selectedCollections.map(collection => collection.contractAddress), - hideUnlistedTokens - }) - - const isSingleCollectionSelected = selectedCollections.length === 1 - - const collectibleBalancesFiltered = tokenBalancesData.filter(token => { - if (isSingleCollectionSelected && selectedCollections[0]) { - // Ensure we only show tokens from the selected collection's chain and contract - return ( - token.chainId === selectedCollections[0].chainId && - token.contractAddress.toLowerCase() === selectedCollections[0].contractAddress.toLowerCase() - ) - } - return true - }) - - const onHandleCollectibleClick = (balance: TokenBalanceWithPrice) => { - setNavigation({ - location: 'collectible-details', - params: { - contractAddress: balance.contractAddress, - chainId: balance.chainId, - tokenId: balance.tokenID || '', - accountAddress: balance.accountAddress - } - }) - } - - // Get the single selected collection details if available - const singleCollection = isSingleCollectionSelected ? selectedCollections[0] : null - - // Prepare the header info object, only if a single collection is selected - const collectionHeaderInfo = singleCollection - ? { - logoURI: singleCollection.logoURI, - name: singleCollection.name, - chainId: singleCollection.chainId - // We could add collectibleBalancesFiltered.length here if needed later - } - : undefined - - return ( -
- -
- ) -} diff --git a/packages/wallet-widget/src/views/Search/SearchTokens.tsx b/packages/wallet-widget/src/views/Search/SearchTokens.tsx deleted file mode 100644 index 6683f10a8..000000000 --- a/packages/wallet-widget/src/views/Search/SearchTokens.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useObservable } from 'micro-observables' - -import { TokenList } from '../../components/SearchLists/index.js' -import { useNavigation, useSettings } from '../../hooks/index.js' -import { useGetAllTokensDetails } from '../../hooks/index.js' -import type { TokenBalanceWithPrice } from '../../utils/index.js' - -export const SearchTokens = () => { - const { setNavigation } = useNavigation() - const { selectedWalletsObservable, selectedNetworksObservable, hideUnlistedTokens } = useSettings() - - const selectedWallets = useObservable(selectedWalletsObservable) - const selectedNetworks = useObservable(selectedNetworksObservable) - - const { data: tokenBalancesData, isLoading } = useGetAllTokensDetails({ - accountAddresses: selectedWallets.map(wallet => wallet.address), - chainIds: selectedNetworks, - hideUnlistedTokens - }) - - const handleTokenClick = (balance: TokenBalanceWithPrice) => { - setNavigation({ - location: 'coin-details', - params: { - contractAddress: balance.contractAddress, - chainId: balance.chainId, - accountAddress: balance.accountAddress - } - }) - } - - return ( -
- -
- ) -} diff --git a/packages/wallet-widget/src/views/Search/index.ts b/packages/wallet-widget/src/views/Search/index.ts deleted file mode 100644 index 41bd2e7b9..000000000 --- a/packages/wallet-widget/src/views/Search/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './SearchTokens.js' -export * from './SearchCollectibles.js' diff --git a/packages/wallet-widget/src/views/Search/index.tsx b/packages/wallet-widget/src/views/Search/index.tsx new file mode 100644 index 000000000..3c496467c --- /dev/null +++ b/packages/wallet-widget/src/views/Search/index.tsx @@ -0,0 +1,5 @@ +import { GeneralList } from '../../components/SearchLists/GeneralList.js' + +export const Search = () => { + return +} diff --git a/packages/wallet-widget/src/views/Send/SendCoin.tsx b/packages/wallet-widget/src/views/Send/SendCoin.tsx index 814cf2785..4bfd17148 100644 --- a/packages/wallet-widget/src/views/Send/SendCoin.tsx +++ b/packages/wallet-widget/src/views/Send/SendCoin.tsx @@ -35,11 +35,11 @@ import { useEffect, useRef, useState, type ChangeEvent } from 'react' import { encodeFunctionData, formatUnits, parseUnits, toHex, zeroAddress, type Hex } from 'viem' import { useAccount, useChainId, useConfig, usePublicClient, useSwitchChain, useWalletClient } from 'wagmi' -import { WalletSelect } from '../../components/Select/WalletSelect.js' +import { AllButActiveWalletSelect } from '../../components/Select/AllButActiveWalletSelect.js' import { SendItemInfo } from '../../components/SendItemInfo.js' import { TransactionConfirmation } from '../../components/TransactionConfirmation.js' import { EVENT_SOURCE, EVENT_TYPES } from '../../constants/analytics.js' -import { ERC_20_ABI, HEADER_HEIGHT_WITH_LABEL } from '../../constants/index.js' +import { ERC_20_ABI } from '../../constants/index.js' import { useNavigationContext } from '../../contexts/Navigation.js' import { useNavigation, useSettings } from '../../hooks/index.js' import { computeBalanceFiat, isEthAddress, limitDecimals } from '../../utils/index.js' @@ -321,13 +321,7 @@ export const SendCoin = ({ chainId, contractAddress }: SendCoinProps) => { } return ( -
+ {!showConfirmation && ( <>
@@ -397,7 +391,7 @@ export const SendCoin = ({ chainId, contractAddress }: SendCoinProps) => { /> } /> - {wallets.length > 1 && } + {wallets.length > 1 && } )}
diff --git a/packages/wallet-widget/src/views/Send/SendCollectible.tsx b/packages/wallet-widget/src/views/Send/SendCollectible.tsx index 37b5bf52c..18e2eaa54 100644 --- a/packages/wallet-widget/src/views/Send/SendCollectible.tsx +++ b/packages/wallet-widget/src/views/Send/SendCollectible.tsx @@ -29,11 +29,11 @@ import { useEffect, useRef, useState, type ChangeEvent } from 'react' import { encodeFunctionData, formatUnits, parseUnits, toHex, type Hex } from 'viem' import { useAccount, useChainId, useConfig, usePublicClient, useSwitchChain, useWalletClient } from 'wagmi' -import { WalletSelect } from '../../components/Select/WalletSelect.js' +import { AllButActiveWalletSelect } from '../../components/Select/AllButActiveWalletSelect.js' import { SendItemInfo } from '../../components/SendItemInfo.js' import { TransactionConfirmation } from '../../components/TransactionConfirmation.js' import { EVENT_SOURCE, EVENT_TYPES } from '../../constants/analytics.js' -import { ERC_1155_ABI, ERC_721_ABI, HEADER_HEIGHT_WITH_LABEL } from '../../constants/index.js' +import { ERC_1155_ABI, ERC_721_ABI } from '../../constants/index.js' import { useNavigationContext } from '../../contexts/Navigation.js' import { useNavigation } from '../../hooks/index.js' import { isEthAddress, limitDecimals } from '../../utils/index.js' @@ -362,13 +362,7 @@ export const SendCollectible = ({ chainId, contractAddress, tokenId }: SendColle const isMaximum = Number(amount) >= Number(maxAmount) return ( - + {!showConfirmation && ( <>
@@ -442,7 +436,7 @@ export const SendCollectible = ({ chainId, contractAddress, tokenId }: SendColle /> } /> - {wallets.length > 1 && } + {wallets.length > 1 && } )}
diff --git a/packages/wallet-widget/src/views/Send/SendGeneral.tsx b/packages/wallet-widget/src/views/Send/SendGeneral.tsx index 213609c74..570418f2c 100644 --- a/packages/wallet-widget/src/views/Send/SendGeneral.tsx +++ b/packages/wallet-widget/src/views/Send/SendGeneral.tsx @@ -1,79 +1,23 @@ import { useWallets } from '@0xsequence/connect' -import { TabsContent, TabsHeader, TabsRoot } from '@0xsequence/design-system' -import { useState } from 'react' +import { useAccount } from 'wagmi' -import { CollectiblesList, TokenList } from '../../components/SearchLists/index.js' -import { useGetAllTokensDetails, useNavigation, useSettings } from '../../hooks/index.js' -import type { TokenBalanceWithPrice } from '../../utils/tokens.js' +import { GeneralList } from '../../components/SearchLists/index.js' +import { WalletSelect } from '../../components/Select/WalletSelect.js' export const SendGeneral = () => { - const { setNavigation } = useNavigation() - const { wallets } = useWallets() - const { allNetworks, hideUnlistedTokens } = useSettings() - const [selectedTab, setSelectedTab] = useState<'coins' | 'collectibles'>('coins') + const { setActiveWallet } = useWallets() + const { address } = useAccount() - const activeWallet = wallets.find(wallet => wallet.isActive) - - const { data: tokenBalancesData = [], isLoading: isLoadingTokenBalances } = useGetAllTokensDetails({ - accountAddresses: [activeWallet?.address || ''], - chainIds: allNetworks, - contractWhitelist: [], - hideUnlistedTokens - }) - - const handleTokenClick = (token: TokenBalanceWithPrice) => { - setNavigation({ - location: 'send-coin', - params: { - chainId: token.chainId, - contractAddress: token.contractAddress - } - }) - } - - const handleCollectibleClick = (token: TokenBalanceWithPrice) => { - setNavigation({ - location: 'send-collectible', - params: { - chainId: token.chainId, - contractAddress: token.contractAddress, - tokenId: token.tokenID || '' - } - }) + const onClickWallet = (address: string) => { + setActiveWallet(address) } return ( -
- setSelectedTab(value as 'coins' | 'collectibles')} - > - - - - - - - - +
+
+ +
+
) } diff --git a/packages/wallet-widget/src/views/Settings/Apps.tsx b/packages/wallet-widget/src/views/Settings/Apps.tsx index cc51aa612..234251c85 100644 --- a/packages/wallet-widget/src/views/Settings/Apps.tsx +++ b/packages/wallet-widget/src/views/Settings/Apps.tsx @@ -1,14 +1,13 @@ -import { OptionsFooter } from '../../components/OptionsFooter.js' -import { useNavigation } from '../../hooks/index.js' +// import { useNavigation } from '../../hooks/index.js' export const SettingsApps = () => { - const { setNavigation } = useNavigation() - const onClickConnectApp = () => { - setNavigation({ location: 'connect-dapp' }) - } + // const { setNavigation } = useNavigation() + // const onClickConnectApp = () => { + // setNavigation({ location: 'connect-dapp' }) + // } return (
- + {/* */}
) } diff --git a/packages/wallet-widget/src/views/Settings/Currency.tsx b/packages/wallet-widget/src/views/Settings/Currency.tsx index 00851c459..15ad26268 100644 --- a/packages/wallet-widget/src/views/Settings/Currency.tsx +++ b/packages/wallet-widget/src/views/Settings/Currency.tsx @@ -1,7 +1,7 @@ import { Text } from '@0xsequence/design-system' import { useObservable } from 'micro-observables' -import { ListCardSelect } from '../../components/ListCard/ListCardSelect.js' +import { ListCard } from '../../components/ListCard/ListCard.js' import { supportedFiatCurrencies } from '../../constants/index.js' import { useSettings } from '../../hooks/index.js' @@ -14,7 +14,8 @@ export const SettingsCurrency = () => {
{supportedFiatCurrencies.map((currency, index) => { return ( - setFiatCurrency && setFiatCurrency(currency)} @@ -23,7 +24,7 @@ export const SettingsCurrency = () => { {currency.symbol} {currency.name.message} - + ) })}
diff --git a/packages/wallet-widget/src/views/Settings/Menu.tsx b/packages/wallet-widget/src/views/Settings/Menu.tsx index dac5631c1..f0fb97991 100644 --- a/packages/wallet-widget/src/views/Settings/Menu.tsx +++ b/packages/wallet-widget/src/views/Settings/Menu.tsx @@ -1,14 +1,16 @@ -import { useWallets } from '@0xsequence/connect' -import { CurrencyIcon, NetworkIcon, Text, WalletIcon } from '@0xsequence/design-system' +import { useSocialLink, useWallets } from '@0xsequence/connect' +import { CurrencyIcon, Text, WalletIcon } from '@0xsequence/design-system' import { StackedIconTag } from '../../components/IconWrappers/StackedIconTag.js' -import { ListCardNav } from '../../components/ListCard/ListCardNav.js' -import { HEADER_HEIGHT } from '../../constants/index.js' +import { ListCard } from '../../components/ListCard/ListCard.js' import { useNavigation, useSettings } from '../../hooks/index.js' +import { useShared } from '../../hooks/useShared.js' export const SettingsMenu = () => { + const { setIsSocialLinkOpen } = useSocialLink() const { wallets } = useWallets() - const { selectedNetworks, fiatCurrency } = useSettings() + const { fiatCurrency } = useSettings() + const { isGuest } = useShared() // const activeWallet = wallets.find(wallet => wallet.isActive) // const isEmbedded = activeWallet?.id.includes('waas') @@ -20,12 +22,6 @@ export const SettingsMenu = () => { }) } - const onClickNetworks = () => { - setNavigation({ - location: 'settings-networks' - }) - } - // const onClickProfiles = () => { // setNavigation({ // location: 'settings-profiles' @@ -44,6 +40,10 @@ export const SettingsMenu = () => { }) } + const onClickGuest = () => { + setIsSocialLinkOpen(true) + } + const onClickPreferences = () => { setNavigation({ location: 'settings-preferences' @@ -59,15 +59,6 @@ export const SettingsMenu = () => { /> ) - const networksPreview = ( - {selectedNetworks.length}} - iconList={selectedNetworks.map(network => { - return `https://assets.sequence.info/images/networks/medium/${network}.webp` - })} - /> - ) - const currencyPreview = ( {fiatCurrency.symbol} {fiatCurrency.sign} @@ -75,38 +66,39 @@ export const SettingsMenu = () => { ) return ( -
+
- + Manage Wallets - - - - - Manage Networks - - - + + Manage Currency - + {/* {isEmbedded && ( - + Manage Profiles - + )} */} - + Preferences - + + {isGuest && ( + + + Link Guest Account + + + )}
) diff --git a/packages/wallet-widget/src/views/Settings/Networks.tsx b/packages/wallet-widget/src/views/Settings/Networks.tsx deleted file mode 100644 index 185570394..000000000 --- a/packages/wallet-widget/src/views/Settings/Networks.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { NetworksFilter } from '../../components/Filter/NetworksFilter.js' - -export const SettingsNetworks = () => { - return ( -
- -
- ) -} diff --git a/packages/wallet-widget/src/views/Settings/Preferences.tsx b/packages/wallet-widget/src/views/Settings/Preferences.tsx index f78c3992a..33ef3f8e6 100644 --- a/packages/wallet-widget/src/views/Settings/Preferences.tsx +++ b/packages/wallet-widget/src/views/Settings/Preferences.tsx @@ -1,8 +1,7 @@ import { Switch, Text } from '@0xsequence/design-system' import { useObservable } from 'micro-observables' -import { ListCardSelect } from '../../components/ListCard/ListCardSelect.js' -import { HEADER_HEIGHT } from '../../constants/index.js' +import { ListCard } from '../../components/ListCard/ListCard.js' import { useSettings } from '../../hooks/index.js' export const SettingsPreferences = () => { @@ -10,17 +9,16 @@ export const SettingsPreferences = () => { const hideUnlistedTokens = useObservable(hideUnlistedTokensObservable) return ( -
- + } - type="custom" onClick={() => setHideUnlistedTokens(!hideUnlistedTokens)} > Hide Unlisted Tokens - +
) } diff --git a/packages/wallet-widget/src/views/Settings/QrScan.tsx b/packages/wallet-widget/src/views/Settings/QrScan.tsx index 79386f1dd..9c9b7875f 100644 --- a/packages/wallet-widget/src/views/Settings/QrScan.tsx +++ b/packages/wallet-widget/src/views/Settings/QrScan.tsx @@ -2,7 +2,6 @@ // import { Scanner } from '@yudiel/react-qr-scanner' // import { ChangeEvent, useState } from 'react' -// import { HEADER_HEIGHT } from '../../constants' // // import { useWalletConnectContext } from '../../contexts/WalletConnect' // import { useNavigation } from '../../hooks' @@ -26,7 +25,7 @@ // } // return ( -//
+//
// // Connect to a DApp using WalletConnect // diff --git a/packages/wallet-widget/src/views/Settings/Wallets.tsx b/packages/wallet-widget/src/views/Settings/Wallets.tsx index 23046e60c..6f81f9aba 100644 --- a/packages/wallet-widget/src/views/Settings/Wallets.tsx +++ b/packages/wallet-widget/src/views/Settings/Wallets.tsx @@ -1,10 +1,9 @@ -import { formatAddress, useOpenConnectModal, useWallets } from '@0xsequence/connect' +import { useOpenConnectModal, useWallets } from '@0xsequence/connect' import { cardVariants, CheckmarkIcon, CloseIcon, cn, Divider, IconButton, Spinner, Text } from '@0xsequence/design-system' import { useState } from 'react' -import { CopyButton } from '../../components/CopyButton.js' -import { MediaIconWrapper } from '../../components/IconWrappers/index.js' -import { ListCardSelect } from '../../components/ListCard/ListCardSelect.js' +import { ListCard } from '../../components/ListCard/ListCard.js' +import { ListCardWallet } from '../../components/ListCard/ListCardWallet.js' import { WalletAccountGradient } from '../../components/WalletAccountGradient.js' export const SettingsWallets = () => { @@ -24,7 +23,7 @@ export const SettingsWallets = () => { className={cn(cardVariants({ clickable: true }), 'flex flex-row justify-between items-center rounded-full h-9')} onClick={onClick} > - + {label}
@@ -51,12 +50,11 @@ export const SettingsWallets = () => { } return ( -
-
+
+
{wallets.length > 1 && ( - { ) } > - wallet.address)} size="sm" isAccount /> + wallet.address)} /> All - + )} {wallets.map(wallet => ( - @@ -94,13 +92,7 @@ export const SettingsWallets = () => { confirmDisconnect(wallet.address)} /> ) } - > - - - {formatAddress(wallet.address)} - - - + /> ))}
@@ -110,7 +102,7 @@ export const SettingsWallets = () => {
onClickAddWallet()} > diff --git a/packages/wallet-widget/src/views/Settings/index.ts b/packages/wallet-widget/src/views/Settings/index.ts index 0eeac9e57..b6757fb21 100644 --- a/packages/wallet-widget/src/views/Settings/index.ts +++ b/packages/wallet-widget/src/views/Settings/index.ts @@ -3,6 +3,5 @@ export * from './Wallets.js' export * from './Profiles.js' export * from './Apps.js' export * from './Currency.js' -export * from './Networks.js' export * from './Preferences.js' // export * from './QrScan' diff --git a/packages/wallet-widget/src/views/Swap/CoinInput.tsx b/packages/wallet-widget/src/views/Swap/CoinInput.tsx deleted file mode 100644 index 8efe6cb02..000000000 --- a/packages/wallet-widget/src/views/Swap/CoinInput.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Button, NumericInput, Text } from '@0xsequence/design-system' -import { useEffect, useState, type ChangeEvent } from 'react' -import { formatUnits } from 'viem' - -import { useSettings, useSwap } from '../../hooks/index.js' -import { decimalsToWei, formatFiatBalance } from '../../utils/formatBalance.js' - -export const CoinInput = ({ type, disabled }: { type: 'from' | 'to'; disabled?: boolean }) => { - const { toCoin, fromCoin, amount, nonRecentAmount, recentInput, setAmount } = useSwap() - const coin = type === 'from' ? fromCoin : toCoin - - const { fiatCurrency } = useSettings() - - const [inputValue, setInputValue] = useState('') - - const fiatBalance = formatFiatBalance( - type === recentInput ? amount : nonRecentAmount, - coin?.price.value || 0, - coin?.contractInfo?.decimals || 18, - fiatCurrency.sign - ) - - useEffect(() => { - if (type === recentInput) { - if (amount > 0) { - setInputValue(formatUnits(BigInt(amount), coin?.contractInfo?.decimals || 18)) - } else if (Number(inputValue)) { - setInputValue('') - } - } else if (type !== recentInput) { - if (nonRecentAmount > 0) { - setInputValue(formatUnits(BigInt(nonRecentAmount), coin?.contractInfo?.decimals || 18)) - } else if (Number(inputValue)) { - setInputValue('') - } - } - }, [recentInput, amount, nonRecentAmount]) - - const handleChange = (ev: ChangeEvent) => { - const { value } = ev.target - const changedValue = Number(value) - setInputValue(value) - setAmount(decimalsToWei(changedValue, coin?.contractInfo?.decimals || 18), type) - } - - const handleMax = () => { - setAmount(Number(formatUnits(BigInt(coin?.balance || 0), coin?.contractInfo?.decimals || 18)), type) - } - - return ( -
- - {fiatBalance && ( - - ~{fiatBalance} - - )} - {type === 'from' && ( -
- ) -} diff --git a/packages/wallet-widget/src/views/Swap/CoinSelect.tsx b/packages/wallet-widget/src/views/Swap/CoinSelect.tsx deleted file mode 100644 index 132d45e92..000000000 --- a/packages/wallet-widget/src/views/Swap/CoinSelect.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { cardVariants, ChevronDownIcon, cn, Text, TokenImage } from '@0xsequence/design-system' -import { AnimatePresence } from 'motion/react' -import { useState } from 'react' -import { useChains } from 'wagmi' - -import { CoinRow } from '../../components/SearchLists/TokenList/CoinRow.js' -import { SlideupDrawer } from '../../components/Select/SlideupDrawer.js' -import { useSettings, useSwap } from '../../hooks/index.js' -import { formatTokenInfo } from '../../utils/formatBalance.js' -import type { TokenBalanceWithPrice } from '../../utils/index.js' - -export const CoinSelect = ({ - selectType, - coinList, - disabled -}: { - selectType: 'from' | 'to' - coinList: TokenBalanceWithPrice[] - disabled?: boolean -}) => { - const { fromCoin, toCoin, setFromCoin, setToCoin } = useSwap() - const { fiatCurrency } = useSettings() - const chains = useChains() - - const selectedCoin = selectType === 'from' ? fromCoin : toCoin - const setSelectedCoin = selectType === 'from' ? setFromCoin : setToCoin - - const [isSelectorOpen, setIsSelectorOpen] = useState(false) - - const { logo, name, symbol, displayBalance } = formatTokenInfo(selectedCoin, fiatCurrency.sign, chains) - - const handleSelect = (coin: TokenBalanceWithPrice) => { - setSelectedCoin(coin) - setIsSelectorOpen(false) - } - - return ( -
-
setIsSelectorOpen(true)} - > -
- - {selectType === 'from' ? 'From' : 'To'} - - {selectedCoin ? ( -
- -
- - {name} - - - {displayBalance} - -
-
- ) : ( - - Select Coin - - )} -
- -
- - - {isSelectorOpen && ( - setIsSelectorOpen(false)}> -
- {coinList.map((coin, index) => ( - - ))} -
-
- )} -
-
- ) -} diff --git a/packages/wallet-widget/src/views/Swap/Swap.tsx b/packages/wallet-widget/src/views/Swap/Swap.tsx index d95a878de..67cccf3db 100644 --- a/packages/wallet-widget/src/views/Swap/Swap.tsx +++ b/packages/wallet-widget/src/views/Swap/Swap.tsx @@ -1,140 +1,5 @@ -import { ArrowRightIcon, Card, cardVariants, cn, IconButton, Spinner, Text } from '@0xsequence/design-system' -import { useGetCoinPrices, useGetExchangeRate, useGetTokenBalancesSummary } from '@0xsequence/hooks' -import { useEffect } from 'react' -import { useAccount, useChainId } from 'wagmi' - -import { NetworkSelect } from '../../components/Select/NetworkSelect.js' -import { HEADER_HEIGHT_WITH_LABEL } from '../../constants/index.js' -import { useSettings, useSwap } from '../../hooks/index.js' - -import { CoinInput } from './CoinInput.js' -import { CoinSelect } from './CoinSelect.js' +import { GeneralList } from '../../components/SearchLists/GeneralList.js' export const Swap = () => { - const { - fromCoin, - toCoin, - isSwapReady, - isSwapQuotePending, - hasInsufficientFunds, - isErrorSwapQuote, - isTxnPending, - switchCoinOrder, - onSubmitSwap, - resetSwapStates - } = useSwap() - const { fiatCurrency } = useSettings() - const { address: userAddress } = useAccount() - const connectedChainId = useChainId() - - useEffect(() => { - return resetSwapStates() - }, []) - - const { data: tokenBalances } = useGetTokenBalancesSummary({ - chainIds: [connectedChainId], - filter: { - accountAddresses: [String(userAddress)], - omitNativeBalances: false - } - }) - - // TODO: add new Lifi endpoints to get buy token as well as supported networks GetLifiChains and GetLifiTokens - - const coinBalances = - tokenBalances?.pages - .flatMap(page => page.balances) - .filter(c => c.contractType !== 'ERC1155' && c.contractType !== 'ERC721') || [] - - const { data: coinPrices = [] } = useGetCoinPrices( - coinBalances.map(token => ({ - chainId: token.chainId, - contractAddress: token.contractAddress - })) - ) - - const { data: conversionRate = 1 } = useGetExchangeRate(fiatCurrency.symbol) - - const coinBalancesWithPrices = coinBalances.map(balance => { - const matchingPrice = coinPrices.find(price => { - const isSameChainAndAddress = - price.token.chainId === balance.chainId && price.token.contractAddress === balance.contractAddress - - const isTokenIdMatch = - price.token.tokenId === balance.tokenID || !(balance.contractType === 'ERC721' || balance.contractType === 'ERC1155') - - return isSameChainAndAddress && isTokenIdMatch - }) - - const priceValue = (matchingPrice?.price?.value || 0) * conversionRate - const priceCurrency = fiatCurrency.symbol - - return { - ...balance, - price: { value: priceValue, currency: priceCurrency } - } - }) - - return ( -
- -
- - - {fromCoin && } - {/* TODO: change out disabled to isTxnPending after new swap api is implemented */} - {isErrorSwapQuote && - (hasInsufficientFunds ? ( - - Insufficient Funds - - ) : ( - - Something went wrong. Try again later or with a different combination of coins. - - ))} - -
-
- -
-
-
- - - {toCoin && } - {isSwapQuotePending && ( -
- - - Fetching best price quote… - -
- )} - {isTxnPending && ( -
- - - Sending swap transaction… - -
- )} -
-
-
- - Swap - -
-
- ) + return } diff --git a/packages/wallet-widget/src/views/SwapCoin/SwapList.tsx b/packages/wallet-widget/src/views/SwapCoin/SwapList.tsx index d34e69e1d..2ff871b38 100644 --- a/packages/wallet-widget/src/views/SwapCoin/SwapList.tsx +++ b/packages/wallet-widget/src/views/SwapCoin/SwapList.tsx @@ -21,7 +21,6 @@ import { formatUnits, zeroAddress, type Hex } from 'viem' import { useAccount, useChainId, usePublicClient, useSwitchChain, useWalletClient } from 'wagmi' import { EVENT_SOURCE, EVENT_TYPES } from '../../constants/analytics.js' -import { HEADER_HEIGHT } from '../../constants/index.js' import { useNavigation } from '../../hooks/index.js' interface SwapListProps { @@ -353,7 +352,7 @@ export const SwapList = ({ chainId, contractAddress, amount, slippageBps }: Swap } return ( -
+
) diff --git a/packages/wallet-widget/src/views/SwapCoin/index.tsx b/packages/wallet-widget/src/views/SwapCoin/index.tsx index d5d258ee0..16fbea0db 100644 --- a/packages/wallet-widget/src/views/SwapCoin/index.tsx +++ b/packages/wallet-widget/src/views/SwapCoin/index.tsx @@ -6,8 +6,8 @@ import { useRef, useState, type ChangeEvent } from 'react' import { parseUnits, zeroAddress } from 'viem' import { useAccount, useConfig } from 'wagmi' +import { WalletSelect } from '../../components/Select/WalletSelect.js' import { SendItemInfo } from '../../components/SendItemInfo.js' -import { HEADER_HEIGHT } from '../../constants/index.js' import { useNavigation, useSettings } from '../../hooks/index.js' import { computeBalanceFiat, limitDecimals } from '../../utils/index.js' @@ -19,7 +19,7 @@ export interface SwapCoinProps { export const SwapCoin = ({ contractAddress, chainId }: SwapCoinProps) => { const { setNavigation } = useNavigation() const { chains } = useConfig() - const { address: accountAddress = '' } = useAccount() + const { address: accountAddress } = useAccount() const amountInputRef = useRef(null) const { fiatCurrency } = useSettings() @@ -89,13 +89,8 @@ export const SwapCoin = ({ contractAddress, chainId }: SwapCoinProps) => { const isNonZeroAmount = amountRaw > 0n return ( - + + {}} disabled />
{ } />
-
+