diff --git a/components/CreateVault/CreateForm.tsx b/components/CreateVault/CreateForm.tsx index ccce8bb..4f682f8 100644 --- a/components/CreateVault/CreateForm.tsx +++ b/components/CreateVault/CreateForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' import { Loader2, ArrowRight } from 'lucide-react' @@ -8,11 +8,10 @@ import { Player } from '@lottiefiles/react-lottie-player' import Animation from '@/public/animations/congrats_animation.json' import { toast } from '../ui/use-toast' import Link from 'next/link' -import { readContract } from '@wagmi/core' +import { readContract } from '@wagmi/core' import { useState, useEffect } from 'react' -import { useAccount } from 'wagmi' +import { useAccount, useChainId } from 'wagmi' import { - getTransactionReceipt, simulateContract, writeContract, } from '@wagmi/core' @@ -22,6 +21,9 @@ import { HodlCoinFactoryAbi } from '@/utils/contracts/HodlCoinFactory' import { ERC20Abi } from '@/utils/contracts/ERC20' import { config } from '@/utils/config' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs' +import { TokenSchema } from '../hooks/useTokenList' +import TokenPicker from './TokenPicker' const BLOCK_EXPLORERS: { [key: number]: string } = { 1: 'https://etherscan.io', @@ -33,6 +35,8 @@ const BLOCK_EXPLORERS: { [key: number]: string } = { export default function CreateForm() { const account = useAccount() + const activeChainId = useChainId(); + const[token,setToken] = useState (null) const [coinName, setCoinName] = useState('') const [symbol, setSymbol] = useState('') const [coin, setCoin] = useState('') @@ -59,26 +63,30 @@ export default function CreateForm() { // Pre-fill vault creator with connected wallet address useEffect(() => { - if (account.address && !vaultCreator) { + if (account.address && !vaultCreator) { setVaultCreator(account.address) } }, [account.address, vaultCreator]) + useEffect(()=>{ + setToken(null); + setCoin(''); + setSymbol('') + },[activeChainId]) + // Fetch symbol from underlying asset and pre-fill vault symbol useEffect(() => { const fetchTokenSymbol = async () => { if (!coin || coin.length !== 42 || !coin.startsWith('0x')) return try { - const chainId = config.state.chainId const tokenSymbol = await readContract(config as any, { abi: ERC20Abi, address: coin as `0x${string}`, functionName: 'symbol', args: [], }) as string - - if (tokenSymbol && !symbol) { + if (tokenSymbol) { setSymbol(`h${tokenSymbol}`) } } catch (error) { @@ -87,7 +95,9 @@ export default function CreateForm() { } fetchTokenSymbol() - }, [coin, symbol]) + }, [coin]) + + const convertFeeToNumerator = (fee: string): bigint => { const feeNumber = parseFloat(fee) @@ -211,7 +221,7 @@ export default function CreateForm() { setLoadingCreation(false) } } - + return (
{/* Enhanced Background Elements with 3D effect */} @@ -244,8 +254,48 @@ export default function CreateForm() { )}
-
- + + + + Select Token + + + Enter Token Contract Address + + + + { + setCoin(token.contract_address) + setToken(token) + setSymbol(`h${token.symbol}`) + + }} + chainId={activeChainId} + placeholder="Select a token to stake" + /> + + + {errors.coin}

)} -
+ + + +
diff --git a/components/CreateVault/TokenPicker.tsx b/components/CreateVault/TokenPicker.tsx new file mode 100644 index 0000000..7a6561b --- /dev/null +++ b/components/CreateVault/TokenPicker.tsx @@ -0,0 +1,375 @@ +"use client"; + +import * as React from "react"; +import { useState, useEffect } from "react"; +import { motion } from "framer-motion"; +import { FixedSizeList as List } from "react-window"; +import { + Search, + X, + AlertCircle, + Loader2, + ChevronDown, + Coins, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; +import { TokenSchema, useTokenList } from "../hooks/useTokenList"; +import { useTokenSearch } from "../hooks/useTokenSearch"; + + +export interface TokenPickerProps { + selected?: TokenSchema | null; + onSelect: (token: TokenSchema) => void; + placeholder?: string; + maxResults?: number; + className?: string; + disabled?: boolean; + chainId: number; +} + + +function HighlightMatch({ text, query }: { text: string; query: string }) { + if (!query.trim()) return <>{text}; + + const regex = new RegExp( + `(${query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, + "gi" + ); + const parts = text.split(regex); + + return ( + <> + {parts.map((part, index) => + regex.test(part) ? ( + + {part} + + ) : ( + part + ) + )} + + ); +} + +const TokenItem = React.memo(function TokenItem({ + token, + query, + isSelected, + onSelect, +}: { + token: TokenSchema; + query: string; + isSelected: boolean; + onSelect: (token: TokenSchema) => void; +}) { + const handleClick = React.useCallback(() => { + onSelect(token); + }, [onSelect, token]); + + return ( + + ); +}); + +// After the TokenItem component and before TokenPicker +interface VirtualizedItemData { + items: TokenSchema[]; + query: string; + selectedAddress?: string; + onSelect: (token: TokenSchema) => void; +} + +interface VirtualizedItemProps { + index: number; + style: React.CSSProperties; + data: VirtualizedItemData; +} + +const VirtualizedTokenItem = React.memo(({ index, style, data }: VirtualizedItemProps) => { + const token = data.items[index]; + return ( +
+ +
+ ); +}); + +VirtualizedTokenItem.displayName = 'VirtualizedTokenItem'; + +export function TokenPicker({ + selected, + onSelect, + placeholder = "Search tokens", + chainId, + className, + disabled = false, +}: TokenPickerProps) { + const [open, setOpen] = useState(false); + const inputRef = React.useRef(null); + const { + tokens, + loading: tokensLoading, + error: tokensError, + } = useTokenList(chainId); + + const { + tokens: filteredTokens, + query, + setQuery, + } = useTokenSearch(tokens); + + useEffect(() => { + if (open && inputRef.current) { + setTimeout(() => inputRef.current?.focus(), 100); + } + }, [open]); + + useEffect(() => { + if (!open) { + setQuery(""); + } + }, [open, setQuery]); + + const handleSelect = (token: TokenSchema) => { + onSelect(token); + setOpen(false); + }; + + return ( + + + + + + + + + + + Select Token + + + +
+
+ + setQuery(e.target.value)} + className="pl-10 pr-10 h-12 bg-muted/50 border-border/50 placeholder:text-muted-foreground + focus-visible:ring-1 focus-visible:ring-primary/30 focus-visible:border-primary/30" + /> + {query && ( + + )} +
+
+ + + +
+ {tokensLoading ? ( +
+
+ + + Loading tokens... + +
+
+ ) : tokensError ? ( +
+
+ + {tokensError.includes("manually input") ? ( + <> + + Testnet:{" "} + {"Citrea's Testnet"} ( + {chainId}) + + + Token Selection is not supported in testnets. + + + {tokensError} + + + ) : tokensError.includes("not supported") ? ( + <> + + Chain Not Supported + + {tokensError} + + ) : ( + <> + Failed to load tokens + + {tokensError} + + + )} +
+
+ ) : filteredTokens.length === 0 ? ( +
+
+ + No tokens found + {query && ( + Try a different search term + )} +
+
+ ) : ( +
+ +{VirtualizedTokenItem} + +
+ )} +
+ + {/* Results count for screen readers */} +
+ {filteredTokens.length} tokens found +
+
+
+
+ ); +} + +export default TokenPicker; diff --git a/components/hooks/useTokenList.ts b/components/hooks/useTokenList.ts new file mode 100644 index 0000000..46fa491 --- /dev/null +++ b/components/hooks/useTokenList.ts @@ -0,0 +1,79 @@ +import { useState, useEffect } from "react"; + +export interface TokenSchema { + id?: string, + symbol: string, + name: string, + image: string, + contract_address: string +} + +const tokenCache :Record ={}; + +const TESTNET_CHAIN_IDS = new Set([5115]); +const isTestNet = (chainId : number) => TESTNET_CHAIN_IDS.has(chainId); + +const ChainIdToName: Record = { + 137: "polygon-pos-tokens", + 1: "ethereum-tokens", + 61: "ethereum-classic-tokens", + 2001: "cardano's-milkomeda-tokens", + 5115: "citrea", +}; + +export function useTokenList(chainId: number) { + const [tokens, setTokens] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchTokens = async () => { + if (tokenCache[chainId]) { + setTokens(tokenCache[chainId]); + return; + } + + setLoading(true); + setError(null); + + if (isTestNet(chainId)) { + setError(`Please manually input the token's contract address instead.`); + setLoading(false); + return; + } + + if (!ChainIdToName[chainId]) { + setError(`Chain ID ${chainId} is not supported yet`); + setLoading(false); + return; + } + + try { + const dataUrl = `https://raw.githubusercontent.com/StabilityNexus/TokenList/main/${ChainIdToName[chainId]}.json`; + const response = await fetch(dataUrl, { + headers: { + Accept: "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch tokens: ${response.statusText}`); + } + + const data = await response.json(); + tokenCache[chainId] = data; + setTokens(data); + } catch (error: unknown) { + console.error("Token fetch error:", error); + setError(error instanceof Error ? error.message : 'Failed to fetch tokens'); + } finally { + setLoading(false); + } + }; + + fetchTokens(); + }, [chainId]); + + return { tokens, loading, error }; + } + \ No newline at end of file diff --git a/components/hooks/useTokenSearch.ts b/components/hooks/useTokenSearch.ts new file mode 100644 index 0000000..75d044f --- /dev/null +++ b/components/hooks/useTokenSearch.ts @@ -0,0 +1,114 @@ +import { useState, useEffect, useMemo } from 'react'; +import { TokenSchema } from './useTokenList'; + +function createSearchIndexes(tokens: TokenSchema[]) { + const indexes = { + bySymbol: new Map(), + bySymbolPrefix: new Map>(), + byNamePrefix: new Map>(), + byAddress: new Map(), + }; + + tokens.forEach(token => { + const symbol = token.symbol.toLowerCase(); + const name = token.name.toLowerCase(); + const address = token.contract_address.toLowerCase(); + + // Exact symbol match index + indexes.bySymbol.set(symbol, token); + + // Symbol prefix index + for (let i = 1; i <= symbol.length; i++) { + const prefix = symbol.slice(0, i); + if (!indexes.bySymbolPrefix.has(prefix)) { + indexes.bySymbolPrefix.set(prefix, new Set()); + } + indexes.bySymbolPrefix.get(prefix)!.add(token); + } + + // Name prefix index + for (let i = 1; i <= name.length; i++) { + const prefix = name.slice(0, i); + if (!indexes.byNamePrefix.has(prefix)) { + indexes.byNamePrefix.set(prefix, new Set()); + } + indexes.byNamePrefix.get(prefix)!.add(token); + } + + // Address index + indexes.byAddress.set(address, token); + }); + + return indexes; +} + +export function useTokenSearch(tokens: TokenSchema[], pageSize: number = 50) { + + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); + + // For debouncing search + const [debouncedQuery, setDebouncedQuery] = useState(""); + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedQuery(query); + setPage(1); // Reset pagination when query changes + }, 250); + return () => clearTimeout(timer); + }, [query]); + + // Create search indexes + const indexes = useMemo(() => createSearchIndexes(tokens), [tokens]); + + const filteredTokens = useMemo(() => { + if (!debouncedQuery.trim()) { + return tokens; // no pagination when empty query + } + + const lowerQuery = debouncedQuery.toLowerCase(); + const results = new Set(); + + // Check exact symbol match + const exactSymbol = indexes.bySymbol.get(lowerQuery); + if (exactSymbol) { + results.add(exactSymbol); + } + + // Check symbol prefix matches + const symbolPrefixMatches = indexes.bySymbolPrefix.get(lowerQuery); + if (symbolPrefixMatches) { + symbolPrefixMatches.forEach(token => results.add(token)); + } + + // Check name prefix matches + const namePrefixMatches = indexes.byNamePrefix.get(lowerQuery); + if (namePrefixMatches) { + namePrefixMatches.forEach(token => results.add(token)); + } + + // Check address matches + if (lowerQuery.length >= 2) { // Only search addresses for queries >= 2 chars + for (const [address, token] of indexes.byAddress.entries()) { + if (address.includes(lowerQuery)) { + results.add(token); + } + } + } + + return Array.from(results).slice(0, page * pageSize); + }, [tokens, debouncedQuery, indexes, page, pageSize]); + + const loadMore = () => { + setPage(prev => prev + 1); + }; + + const hasMore = debouncedQuery.trim() ? filteredTokens.length === page * pageSize : false; + + return { + tokens: filteredTokens, + query, + setQuery, + loadMore, + hasMore, + }; +} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..71e428b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..d9ccec9 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx new file mode 100644 index 0000000..275381c --- /dev/null +++ b/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/package-lock.json b/package-lock.json index 59aeaed..7a4489f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "dependencies": { "@lottiefiles/react-lottie-player": "^3.5.3", "@radix-ui/react-alert-dialog": "^1.1.3", + "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", @@ -19,6 +21,7 @@ "@reown/appkit": "^1.5.3", "@reown/appkit-adapter-wagmi": "^1.5.3", "@tanstack/react-query": "^5.62.0", + "@types/react-window": "^1.8.8", "@wagmi/core": "^2.9.7", "blockies": "^0.0.2", "class-variance-authority": "^0.7.0", @@ -29,9 +32,11 @@ "lucide-react": "^0.377.0", "next": "14.2.3", "next-themes": "^0.3.0", + "pino-pretty": "^13.1.1", "react": "^18", "react-blockies": "^1.4.1", "react-dom": "^18", + "react-window": "^1.8.11", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.21.55", @@ -41,6 +46,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-window": "^1.8.8", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", @@ -1985,6 +1991,33 @@ } } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", @@ -3413,6 +3446,29 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -3964,6 +4020,33 @@ } } }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated/node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", @@ -4528,6 +4611,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6367,6 +6460,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, "node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", @@ -6553,6 +6652,15 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -7697,6 +7805,12 @@ "node": ">=12.0.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8309,6 +8423,12 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -8937,6 +9057,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -9249,6 +9378,12 @@ "@babel/runtime": "^7.12.5" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9327,7 +9462,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10089,6 +10223,69 @@ "split2": "^4.0.0" } }, + "node_modules/pino-pretty": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.1.tgz", + "integrity": "sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pino-std-serializers": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", @@ -10535,6 +10732,23 @@ } } }, + "node_modules/react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -10812,6 +11026,22 @@ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, + "node_modules/secure-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", + "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", diff --git a/package.json b/package.json index ede289d..4c0be4f 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "dependencies": { "@lottiefiles/react-lottie-player": "^3.5.3", "@radix-ui/react-alert-dialog": "^1.1.3", + "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", @@ -20,6 +22,7 @@ "@reown/appkit": "^1.5.3", "@reown/appkit-adapter-wagmi": "^1.5.3", "@tanstack/react-query": "^5.62.0", + "@types/react-window": "^1.8.8", "@wagmi/core": "^2.9.7", "blockies": "^0.0.2", "class-variance-authority": "^0.7.0", @@ -30,15 +33,18 @@ "lucide-react": "^0.377.0", "next": "14.2.3", "next-themes": "^0.3.0", + "pino-pretty": "^13.1.1", "react": "^18", "react-blockies": "^1.4.1", "react-dom": "^18", + "react-window": "^1.8.11", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.21.55", "wagmi": "^2.13.3" }, "devDependencies": { + "@types/react-window": "^1.8.8", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/tsconfig.json b/tsconfig.json index ee33bd8..c3996e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "target":"es6", "lib": [ "dom", "dom.iterable",