diff --git a/frontend/package.json b/frontend/package.json index 4abc1634..61a2806e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,10 +18,13 @@ "@lit-protocol/lit-node-client": "^7.2.0", "@mui/icons-material": "^6.4.6", "@mui/material": "^6.4.6", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.5", "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", "@rainbow-me/rainbowkit": "^2.2.2", "@tanstack/react-query": "^5.64.1", "class-variance-authority": "^0.7.1", @@ -31,7 +34,7 @@ "eth-crypto": "^2.7.0", "ethereum-unit-converter": "^0.0.17", "ethers": "^6.13.5", - "framer-motion": "^12.23.0", + "framer-motion": "^12.23.12", "html2canvas": "^1.4.1", "jspdf": "^3.0.0", "lucide-react": "^0.471.1", @@ -44,6 +47,7 @@ "react-router-dom": "^7.1.1", "react-to-print": "^3.0.5", "react-toastify": "^11.0.5", + "react-window": "^1.8.11", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.22.9", diff --git a/frontend/src/components/TokenCrousel.jsx b/frontend/src/components/TokenCrousel.jsx index ef11603f..d2e5aa53 100644 --- a/frontend/src/components/TokenCrousel.jsx +++ b/frontend/src/components/TokenCrousel.jsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; import { motion } from "framer-motion"; import { SiEthereum } from "react-icons/si"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +// import { TOKEN_PRESETS } from "@/utils/erc20_token"; const TokenCarousel = () => { const carouselRef = useRef(); diff --git a/frontend/src/components/TokenPicker.jsx b/frontend/src/components/TokenPicker.jsx new file mode 100644 index 00000000..90379628 --- /dev/null +++ b/frontend/src/components/TokenPicker.jsx @@ -0,0 +1,384 @@ +import React, { useState, useEffect, useRef, useCallback, memo } from "react"; +import { + Search, + X, + AlertCircle, + Loader2, + ChevronDown, + Coins, + ToggleLeft, + ToggleRight, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useTokenList } from "../hooks/useTokenList"; +import { useTokenSearch } from "../hooks/useTokenSearch"; +import { CopyButton } from "./ui/copyButton"; +import { Avatar } from "./ui/avatar"; +import { Badge } from "./ui/badge"; + +// Simple Modal Component (unchanged) +const Modal = ({ isOpen, onClose, children }) => { + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+
+
+ {children} +
+
+ ); +}; + +// Toggle Switch Component +export const ToggleSwitch = ({ enabled, onChange, leftLabel, rightLabel }) => ( +
+ + {leftLabel} + + + + {rightLabel} + +
+); + +// Highlight Match Component (unchanged) +function HighlightMatch({ text, query }) { + 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 + ) + )} + + ); +} + +// Token Item Component (unchanged) +const TokenItem = memo(function TokenItem({ + token, + query, + isSelected, + onSelect, +}) { + const handleClick = useCallback(() => { + onSelect(token); + }, [onSelect, token]); + + return ( + + ); +}); + +// Main TokenPicker Component +export function TokenPicker({ + selected, + onSelect, + placeholder = "Search tokens", + chainId, + className, + disabled = false, + allowCustom = true, + onCustomTokenClick, +}) { + const [open, setOpen] = useState(false); + const inputRef = 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) => { + onSelect(token); + setOpen(false); + }; + + const handleCustomTokenClick = () => { + if (onCustomTokenClick) { + onCustomTokenClick(); + } + setOpen(false); + }; + + return ( + <> + + + setOpen(false)}> +
+
+ +

+ Select Token +

+ +
+ +
+
+ + setQuery(e.target.value)} + className="pl-10 pr-10 h-12 border-gray-300 text-black" + /> + {query && ( + + )} +
+
+ +
+
+ {tokensLoading ? ( +
+
+ + + Loading tokens... + +
+
+ ) : tokensError ? ( +
+
+ + + Failed to load tokens + + + {tokensError} + +
+
+ ) : filteredTokens.length === 0 ? ( +
+
+ + No tokens found + {query && ( + + Try a different search term + + )} +
+
+ ) : ( +
+ {filteredTokens.map((token) => ( + + ))} +
+ )} +
+ + {allowCustom && ( +
+ +
+ )} +
+
+
+ + ); +} + +export default TokenPicker; diff --git a/frontend/src/components/ui/avatar.jsx b/frontend/src/components/ui/avatar.jsx new file mode 100644 index 00000000..47effbf4 --- /dev/null +++ b/frontend/src/components/ui/avatar.jsx @@ -0,0 +1,19 @@ +const Avatar = ({ src, alt, className = "", children, onError }) => ( +
+ {src ? ( + {alt} + ) : ( +
+ {children} +
+ )} +
+); +export {Avatar} \ No newline at end of file diff --git a/frontend/src/components/ui/badge.jsx b/frontend/src/components/ui/badge.jsx index a687ebad..75dfca81 100644 --- a/frontend/src/components/ui/badge.jsx +++ b/frontend/src/components/ui/badge.jsx @@ -1,34 +1,9 @@ -import * as React from "react" -import { cva } from "class-variance-authority"; - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Badge({ - className, - variant, - ...props -}) { - return (
); -} - -export { Badge, badgeVariants } +const Badge = ({ className = "", children }) => ( + + {children} + +); + +export {Badge} \ No newline at end of file diff --git a/frontend/src/components/ui/copyButton.jsx b/frontend/src/components/ui/copyButton.jsx new file mode 100644 index 00000000..397f5d52 --- /dev/null +++ b/frontend/src/components/ui/copyButton.jsx @@ -0,0 +1,41 @@ +import { Check, Copy } from "lucide-react"; +import { useState } from "react"; + +// Copy Button Component +const CopyButton = ({ textToCopy, className = "" }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = async (e) => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(textToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 500); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + return ( + + ); +}; + +export {CopyButton} \ No newline at end of file diff --git a/frontend/src/components/ui/dialog.jsx b/frontend/src/components/ui/dialog.jsx new file mode 100644 index 00000000..b41b8e1a --- /dev/null +++ b/frontend/src/components/ui/dialog.jsx @@ -0,0 +1,94 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/frontend/src/components/ui/separator.jsx b/frontend/src/components/ui/separator.jsx new file mode 100644 index 00000000..c40b8883 --- /dev/null +++ b/frontend/src/components/ui/separator.jsx @@ -0,0 +1,23 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef(( + { className, orientation = "horizontal", decorative = true, ...props }, + ref +) => ( + +)) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/frontend/src/hooks/useTokenList.js b/frontend/src/hooks/useTokenList.js new file mode 100644 index 00000000..9c42cf7b --- /dev/null +++ b/frontend/src/hooks/useTokenList.js @@ -0,0 +1,86 @@ +import { useState, useEffect } from "react"; + +// Cache object to store tokens by chainId +const tokenCache = {}; + +// Add testnet chain IDs +const TESTNET_CHAIN_IDS = new Set([11155111, 5]); // Sepolia, Goerli + +// Helper function to check if a chain is testnet +const isTestnet = (chainId) => TESTNET_CHAIN_IDS.has(chainId); + +export const ChainIdToName = { + 1: "ethereum", + 61:"ethereum-classic", + 11155111: "sepolia", // For demo purposes +}; + +export function useTokenList(chainId) { + const [tokens, setTokens] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchTokens = async () => { + // Return cached tokens if available + if (tokenCache[chainId]) { + setTokens(tokenCache[chainId]); + return; + } + + setLoading(true); + setError(null); + + // Check if chain is testnet - use local preset for testnets + if (isTestnet(chainId)) { + setError(`Please manually input the token's contract address instead.`); + setLoading(false); + return; + } + + // Check if chain is supported + 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]}-tokens.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(); + console.log(data); + // Transform data to match expected format + const transformedData = data.map((token) => ({ + contract_address: token.contract_address || token.address, + symbol: token.symbol, + name: token.name, + image: token.image || token.logo || "/tokenImages/generic.png", + })); + + // Cache the tokens + tokenCache[chainId] = transformedData; + setTokens(transformedData); + } catch (error) { + 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 }; +} diff --git a/frontend/src/hooks/useTokenSearch.js b/frontend/src/hooks/useTokenSearch.js new file mode 100644 index 00000000..92bcf509 --- /dev/null +++ b/frontend/src/hooks/useTokenSearch.js @@ -0,0 +1,118 @@ +import { useState, useEffect, useMemo } from "react"; + +// Create indexes for token data +function createSearchIndexes(tokens) { + 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, pageSize = 250) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + 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.slice(0, page * pageSize); + } + + 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 = filteredTokens.length === page * pageSize; + + return { + tokens: filteredTokens, + loading, + error, + query, + setQuery, + loadMore, + hasMore, + }; +} diff --git a/frontend/src/page/CreateInvoice.jsx b/frontend/src/page/CreateInvoice.jsx index 5013cc8f..9c846b0b 100644 --- a/frontend/src/page/CreateInvoice.jsx +++ b/frontend/src/page/CreateInvoice.jsx @@ -17,8 +17,10 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { + Badge, CalendarIcon, CheckCircle2, + Coins, Loader2, PlusIcon, XCircle, @@ -37,18 +39,12 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; import WalletConnectionAlert from "../components/WalletConnectionAlert"; +import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker"; +import { CopyButton } from "@/components/ui/copyButton"; function CreateInvoice() { const { data: walletClient } = useWalletClient(); @@ -64,27 +60,15 @@ function CreateInvoice() { const [clientAddress, setClientAddress] = useState(""); // Token selection state - const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [selectedToken, setSelectedToken] = useState(null); const [customTokenAddress, setCustomTokenAddress] = useState(""); const [useCustomToken, setUseCustomToken] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const inputRef = useRef(null); + const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); - const filteredTokens = TOKEN_PRESETS.filter( - (token) => - token.name.toLowerCase().includes(searchTerm.toLowerCase()) || - token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) - ); - const POPULAR_TOKENS = [ - "0x0000000000000000000000000000000000000000", // ETH - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC - "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT - "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI - ]; const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; //sepolia based chainvoice test token (CIN) const [itemData, setItemData] = useState([ @@ -602,7 +586,7 @@ function CreateInvoice() { placeholder="Client Wallet Address" className="w-full mb-4 border-gray-300 text-black" name="clientAddress" - value={clientAddress} + value={clientAddress} onChange={(e) => setClientAddress(e.target.value)} /> @@ -686,7 +670,7 @@ function CreateInvoice() {
-

+

+ {/* Toggle Switch */} + +
- - setSearchTerm(e.target.value)} - /> -
- {!searchTerm && ( - <> -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} + {!useCustomToken ? ( + <> + + { + setSelectedToken({ + address: token.contract_address, + symbol: token.symbol, + name: token.name, + logo: token.image, + decimals: 18, + }); + }} + chainId={account?.chainId || 1} + disabled={loading} + className="w-full" + allowCustom={false} // Remove custom token option from picker since we have toggle + /> + + ) : ( + <> + + + {/* Custom Token Instructions */} +
+
+
+
-
-
- Testnet Token -
- {TOKEN_PRESETS.filter((token) => - TESTNET_TOKEN.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} +
+

+ Custom Token Setup +

+

+ Enter the contract address of the ERC-20 token you + want to use for payments. +

+
    +
  • + • Make sure the token contract is deployed and + verified +
  • +
  • + • Address should start with "0x" followed by 40 + characters +
  • +
  • + • Token will be verified automatically after + entering +
  • +
- - )} -
-
- {searchTerm ? "Search Results" : "All Tokens"} -
-
- {filteredTokens - .filter( - (token) => - !POPULAR_TOKENS.includes(token.address) && - !TESTNET_TOKEN.includes(token.address) - ) - .map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))}
- {filteredTokens.length === 0 && ( -
- No tokens found -
- )}
- -
-
- -
- - Custom Token + { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-12 bg-gray-50 text-gray-700 border-gray-200" + disabled={loading} + /> + + {tokenVerificationState === "verifying" && ( +
+ + + Verifying token contract...
- - - -
+ )} - {!useCustomToken && ( -
- -
-
-
- {selectedToken.name} -
-
- - {selectedToken.name} - - - {selectedToken.symbol} - -
- {selectedToken.address} + {tokenVerificationState === "success" && + verifiedToken && ( +
+
+
+ +
+
+

+ {verifiedToken.name} ( + {verifiedToken.symbol}) +

+ + Verified ✓ + +
+
+ + {verifiedToken.address} + + +
+

+ Decimals: {String(verifiedToken.decimals)} • + Contract verified and ready to use +

+
+
+
+
-
-
-
-
- )} -
- - {useCustomToken && ( -
-
- - { - const address = e.target.value; - setCustomTokenAddress(address); - if (!address || !ethers.isAddress(address)) { - setTokenVerificationState("idle"); - setVerifiedToken(null); - } else if (ethers.isAddress(address)) { - verifyToken(address); - } - }} - className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" - disabled={loading} - /> -

- Enter a valid ERC-20 token contract address -

-
- - {tokenVerificationState === "verifying" && ( -
- - Verifying token... -
- )} + )} - {tokenVerificationState === "success" && verifiedToken && ( -
-
-
- -
-

- {verifiedToken.name} ({verifiedToken.symbol}) -

-

- {verifiedToken.address} -

-

-

Decimals: {String(verifiedToken.decimals)}

-

+ {tokenVerificationState === "error" && ( +
+
+ +
+

+ Token verification failed +

+

+ Please check the contract address and try again. + Make sure it's a valid ERC-20 token. +

+
-
- -
- )} - - {tokenVerificationState === "error" && ( -
-
- -

- Failed to verify token. Please check the address. -

-
-
+ )} + )}
- )} +

@@ -1004,23 +852,41 @@ function CreateInvoice() { verifiedToken ? ( <> Note:{" "} - Payments will be processed in {verifiedToken?.symbol}. - Ensure your client has sufficient balance of this token. + Your client will need to have sufficient balance of the + chosen token to be able to pay your invoice. + + ) : customTokenAddress ? ( + <> + Note:{" "} + Please wait for token verification to complete before + proceeding. ) : ( - "" + <> + Note:{" "} + Enter a valid ERC-20 token contract address above to + proceed. + ) + ) : selectedToken ? ( + <> + Note:{" "} + Your client will need to have sufficient balance of{" "} + {selectedToken.symbol} to be able to pay + your invoice. + ) : ( <> Note:{" "} - Payments will be processed in {selectedToken.symbol}. - Ensure your client has sufficient balance of this token. + Please select a payment token to continue with invoice + creation. )}

+ {/* Invoice Items Section */}
@@ -1145,8 +1011,8 @@ function CreateInvoice() { {totalAmountDue}{" "} {useCustomToken - ? verifiedToken?.symbol - : selectedToken.symbol} + ? verifiedToken?.symbol || "TOKEN" + : selectedToken?.symbol || "TOKEN"}
@@ -1170,7 +1036,6 @@ function CreateInvoice() { )}
-
diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx index e4fbb238..22b8ff1f 100644 --- a/frontend/src/page/GenerateLink.jsx +++ b/frontend/src/page/GenerateLink.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef } from "react"; -import { useAccount, useWalletClient } from "wagmi"; +import { useAccount, useWalletClient } from "wagmi"; import { Copy, Link, @@ -9,54 +9,55 @@ import { Loader2, CheckCircle2, XCircle, -} from "lucide-react"; + Coins, +} from "lucide-react"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; - -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { cn } from "@/lib/utils"; + import WalletConnectionAlert from "@/components/WalletConnectionAlert"; import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; -import { BrowserProvider, ethers } from "ethers"; +import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker"; +import { CopyButton } from "@/components/ui/copyButton"; +import { Badge } from "@/components/ui/badge"; +import { BrowserProvider, ethers } from "ethers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { useTokenList } from "../hooks/useTokenList"; + const GenerateLink = () => { - const { address, isConnected } = useAccount(); - const { data: walletClient } = useWalletClient(); + const { address, isConnected, chainId } = useAccount(); + const { data: walletClient } = useWalletClient(); const [copied, setCopied] = useState(false); const [amount, setAmount] = useState(""); const [description, setDescription] = useState(""); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + // Get tokens from the new hook + const { tokens } = useTokenList(chainId || 1); + // Token selection state - const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [selectedToken, setSelectedToken] = useState(null); const [customTokenAddress, setCustomTokenAddress] = useState(""); const [useCustomToken, setUseCustomToken] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const inputRef = useRef(null); const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); const [loading, setLoading] = useState(false); - const filteredTokens = TOKEN_PRESETS.filter( - (token) => - token.name.toLowerCase().includes(searchTerm.toLowerCase()) || - token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - const POPULAR_TOKENS = [ - "0x0000000000000000000000000000000000000000", // ETH - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC - "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT - "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI - ]; + // Set default token when tokens are loaded + useEffect(() => { + if (tokens.length > 0 && !selectedToken && !useCustomToken) { + // Find ETH or use first token as default + const ethToken = tokens.find( + (token) => + token.symbol.toLowerCase() === "eth" || + token.contract_address === + "0x0000000000000000000000000000000000000000" + ); + setSelectedToken(ethToken || tokens[0]); + } + }, [tokens, selectedToken, useCustomToken]); const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; @@ -75,9 +76,9 @@ const GenerateLink = () => { const params = new URLSearchParams({ clientAddress: address || "", - tokenAddress: tokenToUse.address, - customToken: useCustomToken ? true : false, - chain: "1", + tokenAddress: tokenToUse.address || tokenToUse.contract_address, + customToken: useCustomToken ? "true" : "false", + chain: chainId?.toString() || "1", }); if (amount) { @@ -101,7 +102,7 @@ const GenerateLink = () => { }; const verifyToken = async (address) => { - if (!walletClient) return; + if (!walletClient) return; setTokenVerificationState("verifying"); @@ -121,6 +122,7 @@ const GenerateLink = () => { setTokenVerificationState("error"); } }; + return ( <>
@@ -131,11 +133,7 @@ const GenerateLink = () => { />
-
+

@@ -148,6 +146,7 @@ const GenerateLink = () => { details.

+
@@ -162,300 +161,244 @@ const GenerateLink = () => {
{/* Token Selection */} -
+
+

+ + + + + + + Payment Currency +

+
+ {/* Toggle Switch */} + { + setUseCustomToken(enabled); + if (enabled) { + setSelectedToken(null); + } else { + // Reset to first token when switching back + if (tokens.length > 0) { + const ethToken = tokens.find( + (token) => + token.symbol.toLowerCase() === "eth" || + token.contract_address === + "0x0000000000000000000000000000000000000000" + ); + setSelectedToken(ethToken || tokens[0]); + } + } + }} + leftLabel="Select Token" + rightLabel="Input Custom Token" + /> +
- - setSearchTerm(e.target.value)} - /> -
- {!searchTerm && ( - <> -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} + {!useCustomToken ? ( + <> + + { + setSelectedToken({ + address: token.contract_address, + symbol: token.symbol, + name: token.name, + logo: token.image, + decimals: 18, + }); + }} + chainId={chainId || 1} + disabled={loading} + className="w-full" + allowCustom={false} + /> + + ) : ( + <> + + + {/* Custom Token Instructions */} +
+
+
+
-
-
- Testnet Token -
- {TOKEN_PRESETS.filter((token) => - TESTNET_TOKEN.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} +
+

+ Custom Token Setup +

+

+ Enter the contract address of the ERC-20 token + you want to use for payments. +

+
    +
  • + • Make sure the token contract is deployed and + verified +
  • +
  • + • Address should start with "0x" followed by + 40 characters +
  • +
  • + • Token will be verified automatically after + entering +
  • +
- - )} -
-
- {searchTerm ? "Search Results" : "All Tokens"}
-
- {filteredTokens - .filter( - (token) => - !POPULAR_TOKENS.includes(token.address) && - !TESTNET_TOKEN.includes(token.address) - ) - .map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} -
- {filteredTokens.length === 0 && ( -
- No tokens found -
- )}
- -
-
- -
- - Custom Token - -
-
- - -
+ { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-12 bg-gray-50 text-gray-700 border-gray-200" + disabled={loading} + /> - {!useCustomToken && selectedToken && ( -
- -
-
-
- {selectedToken.name} -
-
- - {selectedToken.name} + {tokenVerificationState === "verifying" && ( +
+ + + Verifying token contract... - - {selectedToken.symbol} - -
- {selectedToken.address} -
-
-
-
- )} -
+ )} - {useCustomToken && ( -
-
- - { - const address = e.target.value; - setCustomTokenAddress(address); - if (!address || !ethers.isAddress(address)) { - setTokenVerificationState("idle"); - setVerifiedToken(null); - } else if (ethers.isAddress(address)) { - verifyToken(address); - } - }} - className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" - disabled={loading} - /> -

- Enter a valid ERC-20 token contract address -

-
- - {tokenVerificationState === "verifying" && ( -
- - Verifying token... -
- )} + {tokenVerificationState === "success" && + verifiedToken && ( +
+
+
+ +
+
+

+ {verifiedToken.name} ( + {verifiedToken.symbol}) +

+ + Verified ✓ + +
+
+ + {verifiedToken.address} + + +
+

+ Decimals: {String(verifiedToken.decimals)}{" "} + • Contract verified and ready to use +

+
+
+
+ +
+ )} - {tokenVerificationState === "success" && verifiedToken && ( -
-
-
- -
-

- {verifiedToken.name} ({verifiedToken.symbol}) -

-

- {verifiedToken.address} -

-

-

- Decimals: {String(verifiedToken.decimals)} + {tokenVerificationState === "error" && ( +

+
+ +
+

+ Token verification failed

-

+

+ Please check the contract address and try + again. Make sure it's a valid ERC-20 token. +

+
-
- -
+ )} + )} +
+
- {tokenVerificationState === "error" && ( -
-
- -

- Failed to verify token. Please check the address. -

-
-
+
+

+ {useCustomToken ? ( + verifiedToken ? ( + <> + + Note: + {" "} + You need to have sufficient balance of{" "} + {verifiedToken.symbol} to be able to + pay invoice. + + ) : customTokenAddress ? ( + <> + + Note: + {" "} + Please wait for token verification to complete before + proceeding. + + ) : ( + <> + + Note: + {" "} + Enter a valid ERC-20 token contract address above to + proceed. + + ) + ) : selectedToken ? ( + <> + Note:{" "} + You need to have sufficient balance of{" "} + {selectedToken.symbol} to be able to + pay invoice. + + ) : ( + <> + Note:{" "} + Please select a payment token to continue with invoice + creation. + )} -

- )} +

+
diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index e3892524..1d61b217 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -40,7 +40,7 @@ import PaidIcon from "@mui/icons-material/CheckCircle"; import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { useTokenList } from "@/hooks/useTokenList"; import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ @@ -56,7 +56,7 @@ function ReceivedInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address, isConnected } = useAccount(); + const { address, isConnected, chainId } = useAccount(); const [loading, setLoading] = useState(true); const [receivedInvoices, setReceivedInvoice] = useState([]); const [fee, setFee] = useState(0); @@ -66,6 +66,10 @@ function ReceivedInvoice() { const [paymentLoading, setPaymentLoading] = useState({}); const [networkLoading, setNetworkLoading] = useState(false); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + + // Get tokens from the hook + const { tokens } = useTokenList(chainId || 1); + const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -75,6 +79,40 @@ function ReceivedInvoice() { setPage(0); }; + // Helper function to get token info + const getTokenInfo = (tokenAddress) => { + if (!tokens || tokens.length === 0) return null; + + return tokens.find( + (token) => + token.contract_address?.toLowerCase() === tokenAddress?.toLowerCase() || + token.address?.toLowerCase() === tokenAddress?.toLowerCase() + ); + }; + + // Helper function to get token logo + const getTokenLogo = (tokenAddress, fallbackLogo) => { + const tokenInfo = getTokenInfo(tokenAddress); + return ( + tokenInfo?.image || + tokenInfo?.logo || + fallbackLogo || + "/tokenImages/generic.png" + ); + }; + + // Helper function to get token decimals + const getTokenDecimals = (tokenAddress, fallbackDecimals = 18) => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.decimals || fallbackDecimals; + }; + + // Helper function to get token symbol + const getTokenSymbol = (tokenAddress, fallbackSymbol = "TOKEN") => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.symbol || fallbackSymbol; + }; + useEffect(() => { const initLit = async () => { try { @@ -96,6 +134,7 @@ function ReceivedInvoice() { }; initLit(); }, []); + useEffect(() => { setShowWalletAlert(!isConnected); }, [isConnected]); @@ -118,6 +157,7 @@ function ReceivedInvoice() { setLoading(false); return; } + const litNodeClient = litClientRef.current; if (!litNodeClient) { alert("Lit client not initialized"); @@ -140,15 +180,6 @@ function ReceivedInvoice() { return; } - // First check if user has any invoices -// if (!res || !Array.isArray(res) || res.length === 0) { -// setReceivedInvoice([]); -// const fee = await contract.fee(); -// setFee(fee); -// return; -// } - - const decryptedInvoices = []; for (const invoice of res) { @@ -168,6 +199,7 @@ function ReceivedInvoice() { console.warn(`Unauthorized access attempt for invoice ${id}`); continue; } + const ciphertext = atob(encryptedStringBase64); const accessControlConditions = [ { @@ -194,6 +226,7 @@ function ReceivedInvoice() { }, }, ]; + const sessionSigs = await litNodeClient.getSessionSigs({ chain: "ethereum", resourceAbilityRequests: [ @@ -219,6 +252,7 @@ function ReceivedInvoice() { return await generateAuthSig({ signer, toSign }); }, }); + const decryptedString = await decryptToString( { accessControlConditions, @@ -229,24 +263,60 @@ function ReceivedInvoice() { }, litNodeClient ); + const parsed = JSON.parse(decryptedString); parsed["id"] = id; parsed["isPaid"] = isPaid; parsed["isCancelled"] = isCancelled; - // Enhance with token details + // Enhance with token details using the new token fetching system if (parsed.paymentToken?.address) { - const tokenInfo = TOKEN_PRESETS.find( - (t) => - t.address.toLowerCase() === - parsed.paymentToken.address.toLowerCase() - ); + const tokenInfo = getTokenInfo(parsed.paymentToken.address); if (tokenInfo) { parsed.paymentToken = { ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, + logo: tokenInfo.image || tokenInfo.logo, + decimals: tokenInfo.decimals || parsed.paymentToken.decimals, + name: tokenInfo.name || parsed.paymentToken.name, + symbol: tokenInfo.symbol || parsed.paymentToken.symbol, }; + } else { + // Fallback: try to fetch token info from blockchain if not in our list + try { + const tokenContract = new ethers.Contract( + parsed.paymentToken.address, + ERC20_ABI, + provider + ); + + const [symbol, name, decimals] = await Promise.all([ + tokenContract + .symbol() + .catch(() => parsed.paymentToken.symbol || "UNKNOWN"), + tokenContract + .name() + .catch(() => parsed.paymentToken.name || "Unknown Token"), + tokenContract + .decimals() + .catch(() => parsed.paymentToken.decimals || 18), + ]); + + parsed.paymentToken = { + ...parsed.paymentToken, + symbol, + name, + decimals: Number(decimals), + logo: "/tokenImages/generic.png", // Generic fallback + }; + } catch (error) { + console.error( + "Failed to fetch token info from blockchain:", + error + ); + // Keep existing data or set defaults + parsed.paymentToken.logo = + parsed.paymentToken.logo || "/tokenImages/generic.png"; + } } } @@ -261,13 +331,14 @@ function ReceivedInvoice() { setFee(fee); } catch (error) { console.error("Fetch error:", error); + setError("Failed to fetch invoices. Please try again."); } finally { setLoading(false); } }; fetchReceivedInvoices(); - }, [walletClient, litReady, address]); + }, [walletClient, litReady, address, tokens]); const payInvoice = async (invoiceId, amountDue, tokenAddress) => { if (!walletClient) { @@ -296,10 +367,7 @@ function ReceivedInvoice() { throw new Error(`Invalid token address: ${tokenAddress}`); } - const tokenInfo = TOKEN_PRESETS.find( - (t) => t.address.toLowerCase() === tokenAddress.toLowerCase() - ); - const tokenSymbol = tokenInfo?.symbol || "Token"; + const tokenSymbol = getTokenSymbol(tokenAddress, "Token"); if (!isNativeToken) { const tokenContract = new Contract(tokenAddress, ERC20_ABI, signer); @@ -589,6 +657,9 @@ function ReceivedInvoice() { src={invoice.paymentToken.logo} alt={invoice.paymentToken.symbol} className="w-5 h-5 mr-2" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : ( {/* Date Column */} - {formatDate(invoice.issueDate)} @@ -758,7 +828,6 @@ function ReceivedInvoice() { Powered by Chainvoice

-

INVOICE @@ -880,6 +949,9 @@ function ReceivedInvoice() { src={drawerState.selectedInvoice.paymentToken.logo} alt={drawerState.selectedInvoice.paymentToken.symbol} className="w-6 h-6 mr-2" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : (
@@ -973,7 +1045,6 @@ function ReceivedInvoice() {
-
Subtotal: @@ -1003,7 +1074,6 @@ function ReceivedInvoice() {
-
-
- Total Amount: - - {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` - : `${drawerState.selectedInvoice.amountDue} ${ - drawerState.selectedInvoice.paymentToken?.symbol - } + ${ethers.formatUnits(fee)} ETH`} - -
-

- -
- -
)} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 46ed070b..bbe2351d 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -23,6 +23,7 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { toast } from "react-toastify"; import { CircularProgress, Skeleton, @@ -44,7 +45,7 @@ import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { useTokenList } from "@/hooks/useTokenList"; import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ @@ -60,7 +61,7 @@ function SentInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address, isConnected } = useAccount(); + const { address, isConnected, chainId } = useAccount(); const [loading, setLoading] = useState(true); const [sentInvoices, setSentInvoices] = useState([]); @@ -73,14 +74,47 @@ function SentInvoice() { const [cancelConfirmOpen, setCancelConfirmOpen] = useState(false); const [invoiceToCancel, setInvoiceToCancel] = useState(null); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); - + + // Get tokens from the hook + const { tokens } = useTokenList(chainId || 1); + + // Helper function to get token info + const getTokenInfo = (tokenAddress) => { + if (!tokens || tokens.length === 0) return null; + + return tokens.find( + (token) => + token.contract_address?.toLowerCase() === tokenAddress?.toLowerCase() || + token.address?.toLowerCase() === tokenAddress?.toLowerCase() + ); + }; + + // Helper function to get token logo + const getTokenLogo = (tokenAddress, fallbackLogo) => { + const tokenInfo = getTokenInfo(tokenAddress); + return ( + tokenInfo?.image || + tokenInfo?.logo || + fallbackLogo || + "/tokenImages/generic.png" + ); + }; + + // Helper function to get token decimals + const getTokenDecimals = (tokenAddress, fallbackDecimals = 18) => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.decimals || fallbackDecimals; + }; + const handleChangePage = (event, newPage) => { setPage(newPage); }; + const handleChangeRowsPerPage = (event) => { setRowsPerPage(+event.target.value); setPage(0); }; + useEffect(() => { const initLit = async () => { try { @@ -102,6 +136,7 @@ function SentInvoice() { }; initLit(); }, []); + useEffect(() => { setShowWalletAlert(!isConnected); }, [isConnected]); @@ -124,7 +159,6 @@ function SentInvoice() { setLoading(false); return; } - // 2. Connect to Lit Node const litNodeClient = litClientRef.current; if (!litNodeClient) { @@ -132,7 +166,6 @@ function SentInvoice() { return; } - // 3. Contract call to get encrypted invoice const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, @@ -168,6 +201,7 @@ function SentInvoice() { console.warn(`Unauthorized access attempt for invoice ${id}`); continue; } + const ciphertext = atob(encryptedStringBase64); const accessControlConditions = [ { @@ -236,20 +270,58 @@ function SentInvoice() { parsed["id"] = id; parsed["isPaid"] = isPaid; parsed["isCancelled"] = isCancelled; + + // Enhance with token details using the new token fetching system if (parsed.paymentToken?.address) { - const tokenInfo = TOKEN_PRESETS.find( - (t) => - t.address.toLowerCase() === - parsed.paymentToken.address.toLowerCase() - ); + const tokenInfo = getTokenInfo(parsed.paymentToken.address); if (tokenInfo) { parsed.paymentToken = { ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, + logo: tokenInfo.image || tokenInfo.logo, + decimals: tokenInfo.decimals || parsed.paymentToken.decimals, + name: tokenInfo.name || parsed.paymentToken.name, + symbol: tokenInfo.symbol || parsed.paymentToken.symbol, }; + } else { + // Fallback: try to fetch token info from blockchain if not in our list + try { + const tokenContract = new ethers.Contract( + parsed.paymentToken.address, + ERC20_ABI, + provider + ); + + const [symbol, name, decimals] = await Promise.all([ + tokenContract + .symbol() + .catch(() => parsed.paymentToken.symbol || "UNKNOWN"), + tokenContract + .name() + .catch(() => parsed.paymentToken.name || "Unknown Token"), + tokenContract + .decimals() + .catch(() => parsed.paymentToken.decimals || 18), + ]); + + parsed.paymentToken = { + ...parsed.paymentToken, + symbol, + name, + decimals: Number(decimals), + logo: "/tokenImages/generic.png", // Generic fallback + }; + } catch (error) { + console.error( + "Failed to fetch token info from blockchain:", + error + ); + // Keep existing data or set defaults + parsed.paymentToken.logo = + parsed.paymentToken.logo || "/tokenImages/generic.png"; + } } } + decryptedInvoices.push(parsed); } catch (err) { console.error(`Error processing invoice ${invoice[0]}:`, err); @@ -261,6 +333,7 @@ function SentInvoice() { setFee(fee); } catch (error) { console.error("Decryption error:", error); + setError("Failed to fetch invoices. Please try again."); } finally { console.log(sentInvoices); setLoading(false); @@ -268,7 +341,7 @@ function SentInvoice() { }; fetchSentInvoices(); - }, [walletClient, litReady, address]); + }, [walletClient, litReady, address, tokens]); // Added tokens to dependency array const [drawerState, setDrawerState] = useState({ open: false, @@ -301,6 +374,7 @@ function SentInvoice() { link.href = data; link.click(); }; + const handleCancelInvoice = async (invoiceId) => { try { setPaymentLoading((prev) => ({ ...prev, [invoiceId]: true })); @@ -328,6 +402,7 @@ function SentInvoice() { setPaymentLoading((prev) => ({ ...prev, [invoiceId]: false })); } }; + const switchNetwork = async () => { try { setNetworkLoading(true); @@ -517,6 +592,9 @@ function SentInvoice() { src={invoice.paymentToken.logo} alt={invoice.paymentToken.symbol} className="w-5 h-5 mr-2 rounded-full" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : ( { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : (
diff --git a/frontend/src/utils/erc20_token.js b/frontend/src/utils/erc20_token.js deleted file mode 100644 index db81f84e..00000000 --- a/frontend/src/utils/erc20_token.js +++ /dev/null @@ -1,716 +0,0 @@ -// { -// name: "Chainvoice", -// symbol: "CIN", -// address: "0xB5E9C6e57C9d312937A059089B547d0036c155C7", -// decimals: 18, -// }, - -export const TOKEN_PRESETS = [ - { - name: "Chainvoice Testnet Token", - symbol: "CIN", - address: "0xB5E9C6e57C9d312937A059089B547d0036c155C7", - decimals: 18, - logo: "/tokenImages/cin.png", - }, - { - name: "Ethereum Mainnet", - symbol: "ETH", - address: "0x0000000000000000000000000000000000000000", - decimals: 18, - logo: "/tokenImages/eth.png", - }, - { - name: "Tether USD", - symbol: "USDT", - address: "0xdac17f958d2ee523a2206206994597c13d831ec7", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x636c00eefcf239bf56cc07dfc8ec2a0d26ecc765805f8d9e62c866b0164ccb62.png", - }, - { - name: "BNB", - symbol: "BNB", - address: "0xb8c77482e45f1f44de1745f52c74426c631bdd52", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x533cc6c47bad45528f615b1f66023b1f7c429d4304e2d1ddb84918077e8ead8b.png", - }, - { - name: "USD Coin", - symbol: "USDC", - address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xda4a7aa3c2c6966c74c4a8446b6348c3e397491e14b2874719192fa5a4c71cab.png", - }, - { - name: "Liquid staked Ether 2.0", - symbol: "stETH", - address: "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4e05bd4f46441ab8812874ddb4a8e6ace80ab398e09c07086a93e60ff4b7f4bb.png", - }, - { - name: "Wrapped BTC", - symbol: "WBTC", - address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xbd2860a79e7bc89d86922c8a80cdeb9a32a0082045fb0da24c31e3871916e0d3.png", - }, - { - name: "Wrapped liquid staked Ether 2.0", - symbol: "wstETH", - address: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3454daef9545c526928d9a01030752ff1995f65f4ec044a0b127b16f913e4065.png", - }, - { - name: "ChainLink Token", - symbol: "LINK", - address: "0x514910771af9ca656af840dff83e8264ecf986ca", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0d5bccf38448bc46b1a49419cfa43d1d569dbce172c214bacde2d2e22b005dc0.png", - }, - { - name: "Bitfinex LEO Token", - symbol: "LEO", - address: "0x2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0f5926985ff75e8c8caaa95501ea575923c317bb847421d4d00dbf5964a1feea.png", - }, - { - name: "USDS Stablecoin", - symbol: "USDS", - address: "0xdc035d45d973e3ec169d2276ddab16f1e407384f", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb547d1b9a9e8f112d440784ffaed3dbdc433a70820a359614664ec981752ae4e.png", - }, - { - name: "SHIBA INU", - symbol: "SHIB", - address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x934042eb1c76a54280d40bebcc15138690c0b719eaaa4bd07529ce5d8dede7e6.png", - }, - { - name: "EtherFi wrapped ETH", - symbol: "weETH", - address: "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x8a7377a984da522f6099b1a7d8314a6fb527d07cfd637ababfbab4b94bfe693a.png", - }, - { - name: "Wrapped TON Coin", - symbol: "TONCOIN", - address: "0x582d872a1b094fc48f5de31d3b73f2d9be47def1", - decimal: "9", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x5b41e3f6b7eaa6d3ae160a0d3525678c720b1948ca6734dd4d998f2d6310ff1b.png", - }, - { - name: "Wrapped Ether", - symbol: "WETH", - address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7f71adae0bcc9186e966ba6fe0be5dfd303f6ccba90512991f91babf1f333625.png", - }, - { - name: "WBT", - symbol: "WBT", - address: "0x925206b8a707096ed26ae47c84747fe0bb734f59", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xdeea9fdad65441cbb8ed321961502ca3f60ce0e6939d6f3761a6c1d4989a61ec.png", - }, - { - name: "Coinbase Wrapped BTC", - symbol: "cbBTC", - address: "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x12601a638d95d1bed9ee4d1973daad3d89925f85ea42a09951daceb9494645a6.png", - }, - { - name: "USDe", - symbol: "USDe", - address: "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x26c646b32ea6353a192f6c760b97beb706772d0e28099f504520f70a31e3a466.png", - }, - { - name: "BitgetToken", - symbol: "BGB", - address: "0x19de6b897ed14a376dda0fe53a5420d2ac828a28", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xef3652281dd019ae4dd24ec58d13d40c6d0115eb6ed972108808e361bd9db3e1.png", - }, - { - name: "Uniswap", - symbol: "UNI", - address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc53061256eb8145b0a928ac83ee76b055f7c563ad686521367bd16dd20ec3bed.png", - }, - { - name: "Aave Token", - symbol: "AAVE", - address: "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xeab69f3d8298f4b4b826af873d42063dbb05350742033769837aa0be6fbb9b64.png", - }, - { - name: "Pepe", - symbol: "PEPE", - address: "0x6982508145454ce325ddbe47a25d4ec3d2311933", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x673e4e814b3ed3008059e214b80fba443dae310acc14e66f707360067b4424a9.png", - }, - { - name: "Dai Stablecoin", - symbol: "DAI", - address: "0x6b175474e89094c44da98b954eedeac495271d0f", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xea5be93d23c21846ac28d0096ce859aceaac4471a4492818d06c0c5a5e540644.png", - }, - { - name: "Staked USDe", - symbol: "sUSDe", - address: "0x9d39a5de30e57443bff2a8307a4256c8797a3497", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3abf76c30b11bc875bc5460a1f9b52e9d8a203f93c1983eb395afc5a8ba7027f.png", - }, - { - name: "CRO", - symbol: "CRO", - address: "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x25095b588aae7d80740f215b6e69966926d3a8994f141e1593a5c3294f042297.png", - }, - { - name: "OKB", - symbol: "OKB", - address: "0x75231f58b43240c9718dd58b4967c5114342a86c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x80ca2e5c6480ba7147551c1327ac2bfd1214607e90348784d37d84d724b8c5e5.png", - }, - { - name: "BlackRock USD Institutional Digital Liquidity Fund", - symbol: "BUIDL", - address: "0x7712c34205737192402172409a8f7ccef8aa2aec", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xe7568c777cc874c0f00c7528f1c03bc2d9c51ede922116ce57e0941c8b0100b8.png", - }, - { - name: "NEAR", - symbol: "NEAR", - address: "0x85f17cf997934a597031b2e18a9ab6ebd4b9f6a4", - decimal: "24", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc75188cae4539f032ee7e1174ba4c7c9154e1dfbfb855c861f96efaab6ec7258.png", - }, - { - name: "Ondo", - symbol: "ONDO", - address: "0xfaba6f8e4a5e8ab82f62fe7c39859fa577269be3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x1fb261c892aa9b241652d883258be36cc4b87a6f34674e7ea7dc334523b80121.png", - }, - { - name: "Savings USDS", - symbol: "sUSDS", - address: "0xa3931d71877c0e7a3148cb7eb4463524fec27fbd", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xd3a649f8632bc78ce570ce63e898c6b1b0e13082bdfd528f82d46d27b8c09114.png", - }, - { - name: "World Liberty Financial USD", - symbol: "USD1", - address: "0x8d0d000ee44948fc98c9b98a4fa4921476f08b0d", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf53fefeddd8477c2879e714bfae64d77fd05a5099c91d3a88502f4b404b5a710.png", - }, - { - name: "Mantle", - symbol: "MNT", - address: "0x3c3a81e81dc49a522a592e7622a7e711c06bf354", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x22f5817a14887c89254d451c8f2fbcd605d6c9b922fa1897bd0b83f9b335a56b.png", - }, - { - name: "Fasttoken", - symbol: "FTN", - address: "0xaedf386b755465871ff874e3e37af5976e247064", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x53ff0289d0573bc4b047b0fd00e4d7a0b724533af5400f544009f4c503308b5e.png", - }, - { - name: "GateChainToken", - symbol: "GT", - address: "0xe66747a101bff2dba3697199dcce5b743b454759", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x28dee88c251fffa5e1c340e8ddd0689c0d1d0b5d225d47ef709a30aed05879c9.png", - }, - { - name: "Polygon Ecosystem Token", - symbol: "POL", - address: "0x455e53cbb86018ac2b8092fdcd39d8444affc3f6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf5dfa373e5ab92008519c13cddc2505a8679c49cf490ba06ba8365936a06701b.png", - }, - { - name: "Fetch", - symbol: "FET", - address: "0xaea46a60368a7bd060eec7df8cba43b7ef41ad85", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x86707a37ebedf7ec6fc725027f04d0c38cd720d0dc38795877093b97613afbd8.png", - }, - { - name: "ENA", - symbol: "ENA", - address: "0x57e114b691db790c35207b2e685d4a43181e6061", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x90dde53b21e0318435509c0e32190cf31da0971ba874f695b02107f992c9fdc5.png", - }, - { - name: "SKY Governance Token", - symbol: "SKY", - address: "0x56072c95faa701256059aa122697b133aded9279", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x1f6ec6ef067dc18c953e2e43c86923a53b9c9a966166502b3f3c4bb463eed240.png", - }, - { - name: "Render Token", - symbol: "RNDR", - address: "0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x6e69899378586303df5fcb70a6488e60828c68706fd1b83a8954853fe40222f2.png", - }, - { - name: "Arbitrum", - symbol: "ARB", - address: "0xb50721bcf8d664c30412cfbc6cf7a15145234ad1", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xa11a48d3dac41fa9f07bee709903fbf6405f7ce48c6e6dc797e532efeda337d9.png", - }, - { - name: "Lombard Staked Bitcoin", - symbol: "LBTC", - address: "0x8236a87084f8b84306f72007f36f2618a5634494", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3e5e975c15cd33f005ecaa28f7e23cddb1af50d3f85090fdf28e80c77e089a29.png", - }, - { - name: "Quant", - symbol: "QNT", - address: "0x4a220e6096b25eadb88358cb44068a3248254675", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xae8315d6bbe7c190cc699b64db3ecdbc0bce3db790732e7bb67718b58e3c0426.png", - }, - { - name: "Bonk", - symbol: "Bonk", - address: "0x1151cb3d861920e07a38e03eead12c32178567f6", - decimal: "5", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x35fcbf9794295309281d4c0cc759e240a586111e49cd6cdf0cb82499c37dacc0.png", - }, - { - name: "Worldcoin", - symbol: "WLD", - address: "0x163f8c2467924be0ae7b5347228cabf260318753", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb444942fc451b3f471944b51d9be5ada722671cba9b9bce652f95ed1d01ef387.png", - }, - { - name: "USDtb", - symbol: "USDtb", - address: "0xc139190f447e929f090edeb554d95abb8b18ac1c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x80db354588d993be46ec08fdadf0fe3954140e29d185b5c75217ac9556f06420.png", - }, - { - name: "First Digital USD", - symbol: "FDUSD", - address: "0xc5f0f7b66764f6ec8c8dff7ba683102295e16409", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3dba518552999dd3a3b923b18ce39deba017998d7862bffaaabf10f3ebb74470.png", - }, - { - name: "SPX6900", - symbol: "SPX", - address: "0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7c69f4632587621b26bd220686a2bd668f1fdf1937b28167d08b075e9d9da281.png", - }, - { - name: "rsETH", - symbol: "rsETH", - address: "0xa1290d69c65a6fe4df752f95823fae25cb99e5a7", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4d7847e6cfdf7a7b5d1b9d6c7bfc63e067d14ff2dfc6aac731302dec0f16f5a0.png", - }, - { - name: "Rocket Pool ETH", - symbol: "rETH", - address: "0xae78736cd615f374d3085123a210448e74fc6393", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x9ae7abade50f86dc63e72cf2add44c92ab2d3950f42245cb76ffe7dc2e77cb6d.png", - }, - { - name: "Nexo", - symbol: "NEXO", - address: "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaf742d00a2e8d58b2c9341fd73f42cfc2f2e5ae39005533c2aa8d1eeed0a489d.png", - }, - { - name: "Injective Token", - symbol: "INJ", - address: "0xe28b3b32b6c345a34ff64674606124dd5aceca30", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x5aabea3f7de09d6b2a3e861cd97902ba0d341e8f5daa71c9e9ede234d56cf9a5.png", - }, - { - name: "mETH", - symbol: "mETH", - address: "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x65a213ed5544f36d32261e2f1b45a2aa8ce18e674c788687bafbe1d7b303acac.png", - }, - { - name: "Staked ETH", - symbol: "osETH", - address: "0xf1c9acdc66974dfb6decb12aa385b9cd01190e38", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfb9cdd2c50e3cf5ad5452714caaa98a8076975a9bd07d69068f6be235fe814aa.png", - }, - { - name: "Solv BTC", - symbol: "SolvBTC", - address: "0x7a56e1c57c7475ccf742a1832b028f0456652f97", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaab141f44b3f0d76f3315866e19ef7631d74f58c22916af091896824b59e3496.png", - }, - { - name: "Tokenize Emblem", - symbol: "TKX", - address: "0x667102bd3413bfeaa3dffb48fa8288819e480a88", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf7fed58cf6ffda940c013bbb37bfbf319c0c155f54a2a9073156735c0c55a3f0.png", - }, - { - name: "Virtual Protocol", - symbol: "VIRTUAL", - address: "0x44ff8620b8ca30902395a7bd3f2407e1a091bf73", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xab89b951d480f6a2548f6fd2abfe8f13ecc4189fd811cd44c7ff41fa8b5cb1c6.png", - }, - { - name: "Syrup USDC", - symbol: "syrupUSDC", - address: "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb7f804c79a04a9b48473c29cdfc51e9780a52623c5672abb8a48d4056d17beb6.png", - }, - { - name: "Paxos Gold", - symbol: "PAXG", - address: "0x45804880de22913dafe09f4980848ece6ecbaf78", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb95043411156e0cf0a718f9938a17c09a077d3bb7c2b23e87086388a50e0ad69.png", - }, - { - name: "PayPal USD", - symbol: "PYUSD", - address: "0x6c3ea9036406852006290770bedfcaba0e23a0e8", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x61d41978836bfe9cd6810f33bc4833cdde140fbfe1ddd5d5a0cf23d80d9e3c61.png", - }, - { - name: "FLOKI", - symbol: "FLOKI", - address: "0xcf0c122c6b73ff809c693db761e7baebe62b6a2e", - decimal: "9", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xef24c2a03a5e2659fe073768e7b266215411dee6bb50bfa6ae88e0b0b3ed5915.png", - }, - { - name: "Graph Token", - symbol: "GRT", - address: "0xc944e90c64b2c07662a292be6244bdf05cda44a7", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xbcf1ff7bcf39ea3fdc2944b48e335f8a016ca1390c2f9ab3375d592c2a7a284e.png", - }, - { - name: "clBTC", - symbol: "clBTC", - address: "0xe7ae30c03395d66f30a26c49c91edae151747911", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf5890b52fc0e86a586337ccc8e49e4f5f512ab5c00778f5642b3de7cdcf66d4b.png", - }, - { - name: "Tether Gold", - symbol: "XAUt", - address: "0x68749665ff8d2d112fa859aa293f07a622782f38", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xeb1e86b05e387d0ba5081ba78b6d6e42043b36555e443a1293f0bc476afa124c.png", - }, - { - name: "Immutable X", - symbol: "IMX", - address: "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x582820f7558c1a76a1edf7e58df994e265f323bf4020d62d344093e9c176154b.png", - }, - { - name: "PancakeSwap Token", - symbol: "Cake", - address: "0x152649ea73beab28c5b49b26eb48f7ead6d4c898", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x11d48fd825024aa8cfa4d43cc6bd330b0e6a21d5e265a0fbd3d5bb255eb9e69d.png", - }, - { - name: "Liquid Staked ETH", - symbol: "LsETH", - address: "0x8c1bed5b9a0928467c9b1341da1d7bd5e10b6549", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0d8f81393bb459317dac5773b83bb2d790b8a4567b58daf941ea06ff69a56fa9.png", - }, - { - name: "Curve DAO Token", - symbol: "CRV", - address: "0xd533a949740bb3306d119cc777fa900ba034cd52", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc6a36535595fc2a85d6c076ec06df02d3cc28d2399b409c7a7ae58e04367d976.png", - }, - { - name: "Ondo Short-Term U.S. Government Bond Fund", - symbol: "OUSG", - address: "0x1b19c19393e2d034d8ff31ff34c81252fcbbee92", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x368c45405136c537b8cd38c87e5495d88399b388e15a8c05065b548b8de5c7a0.png", - }, - { - name: "Superstate Short Duration US Government Securities Fund", - symbol: "USTB", - address: "0x43415eb6ff9db7e26a15b704e7a3edce97d31c4e", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3a42a31b349dbcc4545784c9083717db6eb96117bd5e046e9c90f8a5121d3fb5.png", - }, - { - name: "USDX", - symbol: "USDX", - address: "0xf3527ef8de265eaa3716fb312c12847bfba66cef", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaebcf05a1df94e754c2f21331f1b10056204bd810e2a015c2ec6e78ba2ba25e0.png", - }, - { - name: "Lido DAO Token", - symbol: "LDO", - address: "0x5a98fcbea516cf06857215779fd812ca3bef1b32", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb003c5c36c3bc6ab6a04cf816bcadf7c69763f51e95169652e8dbd51c4bf09ef.png", - }, - { - name: "Gala", - symbol: "GALA", - address: "0xd1d2eb1b1e90b638588728b4130137d262c87cae", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4031e94d1b2221342425981938d79c0824065b9e0ca604da7594c2ffeb66fe52.png", - }, - { - name: "Ethereum Name Service", - symbol: "ENS", - address: "0xc18360217d8f7ab5e7c516566761ea12ce7f9d72", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x8b1881f819ba5e5e8b986832b569d7c2e0ce1453572ce65d8d41709bec018d94.png", - }, - { - name: "Ondo U.S. Dollar Yield", - symbol: "USDY", - address: "0x96f6ef951840721adbf46ac996b59e0235cb985c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfd4029f0337c74afcab7b96cf7491132f4653636b3d7944acae9cc27ee23e776.png", - }, - { - name: "SAND", - symbol: "SAND", - address: "0x3845badade8e6dff049820680d1f14bd3903a5d0", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3d3aa642e221f1362236429e6c61d46559bb680fe5e30891e8495471a1c9cb6d.png", - }, - { - name: "BitTorrent", - symbol: "BTT", - address: "0xc669928185dbce49d2230cc9b0979be6dc797957", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc4ba29a5dc7368f01ce67ed8ebc9019f0f67bde7cbfabe8ff6157da67aeb99e0.png", - }, - { - name: "JasmyCoin", - symbol: "JASMY", - address: "0x7420b4b9a0110cdc71fb720908340c03f9bc03ec", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf02f7e6ab17a0563cb628067a587959fa6875b4c489a004ba078030583e19ad7.png", - }, - { - name: "Pendle", - symbol: "PENDLE", - address: "0x808507121b80c02388fad14726482e061b8da827", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x93bfdf82043b3dcfd0ecbde84ec9ec332b832168f479c17ceda39ee9429e0338.png", - }, - { - name: "Usual USD", - symbol: "USD0", - address: "0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0e1df7dd5a2d894870a16cd5ccaeb51a240c1f473761915219565a6a993857e5.png", - }, - { - name: "cmETH", - symbol: "cmETH", - address: "0xe6829d9a7ee3040e1276fa75293bde931859e8fa", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x2db801d03445f6a78a4eaa4c5f1ac075c66e89fb42f1312d454a7e759df774d5.png", - }, - { - name: "SolvBTC Babylon", - symbol: "SolvBTC.BBN", - address: "0xd9d920aa40f578ab794426f5c90f6c731d159def", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x07bd4c851a9dd9bf2731542a39e6986017f2dc41350a6ff4447d84e36655fe1a.png", - }, - { - name: "tBTC v2", - symbol: "tBTC", - address: "0x18084fba666a33d37592fa2633fd49a74dd93a88", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x374c55831315eece6acdf73dd0ed4342ca11ba7a43146a2814f75b3cd47f02b4.png", - }, - { - name: "cgETH Hashkey Cloud", - symbol: "cgETH.hashkey", - address: "0xc60a9145d9e9f1152218e7da6df634b7a74ae444", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x518d0de9c0610837259b1bafb438bec7e0d08d6d50472fd1f16fe150c9c686b9.png", - }, - { - name: "Syrup Token", - symbol: "SYRUP", - address: "0x643c4e15d7d62ad0abec4a9bd4b001aa3ef52d66", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x53a946528e8af04d1cf5aeb25bff5b60cb3d619a771a47ae8573de1c52fcff54.png", - }, - { - name: "Decentraland MANA", - symbol: "MANA", - address: "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3de2fd9036d3330aa44d05bed8e7d42c78763c78b7e9c67ec9e57763cfe007ab.png", - }, - { - name: "", - symbol: "", - address: "0xcfd748b9de538c9f5b1805e8db9e1d4671f7f2ec", - decimal: "0", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x11e6eff08e149134d03f121f37913a360168286298c72c99aa2c7a6e78182205.png", - }, - { - name: "TrueUSD", - symbol: "TUSD", - address: "0x0000000000085d4780b73119b644ae5ecd22b376", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfdee06ccdc1b94b47884cb37f623663824e23ad500f601cc50c7419b266b68d9.png", - }, - { - name: "ApeCoin", - symbol: "APE", - address: "0x4d224452801aced8b2f0aebe155379bb5d594381", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x97863e7e4f8a71de7dffecb21913a33f7af57a7170fbfeff05870f2acb645da2.png", - }, - { - name: "Chain", - symbol: "XCN", - address: "0xa2cd3d43c775978a96bdbf12d733d5a1ed94fb18", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x321ed57969e51fa8315f370d76414baa8fe2fb9b079ce3ca5bfaccead729fca5.png", - }, - { - name: "Morpho Token", - symbol: "MORPHO", - address: "0x9994e35db50125e0df82e4c2dde62496ce330999", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xe90be6230ef4ccee86e8b87c0ca8d24f8cf14bead8f1621ce3456135ffd3cc85.png", - }, - { - name: "Decentralized USD", - symbol: "USDD", - address: "0x0c10bf8fcb7bf5412187a595ab97a3609160b5c6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xac24f1bff2cf09d84ce133faf5e5428994ad07efc23b65d8779c09dd8c0f7cff.png", - }, - { - name: "Dexe", - symbol: "DEXE", - address: "0xde4ee8057785a7e8e800db58f9784845a5c2cbd6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x95ccb485493b37dbf2e6a86496688d00ef15614710b2841904dd1a9371fbfd66.png", - }, - { - name: "Mog Coin", - symbol: "Mog", - address: "0xaaee1a9723aadb7afa2810263653a34ba2c21c7a", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x2901a28b39db8fc38d87ca82a3a54efcfa312e737501dd4ca68dda7cf8fbd275.png", - }, - { - name: "APENFT", - symbol: "NFT", - address: "0x198d14f2ad9ce69e76ea330b374de4957c3f850a", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf48a34674e86da1610277474d53e92a21d74b5385215c84dc874c6098cd41f80.png", - }, - { - name: "Reserve Rights", - symbol: "RSR", - address: "0x320623b8e4ff03373931769a31fc52a4e78b5d70", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x79fb26c2f551cc14b32575f693298d3d30e2a87c242344cca29e527fa39a7786.png", - }, - { - name: "StarkNet Token", - symbol: "STRK", - address: "0xca14007eff0db1f8135f4c25b34de49ab0d42766", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x9a122beff8685e1b1ccd7a2b55b1f923b74eb7c37a77e58fca918937fe72c4ae.png", - }, - { - name: "ETHx", - symbol: "ETHx", - address: "0xa35b1b31ce002fbf2058d22f30f95d405200a15b", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7ecb25bb3cb2e648b6d601ab906e5bc27a43709ff0818d471043828f7738b98f.png", - }, - { - name: "US Yield Coin", - symbol: "USYC", - address: "0x136471a34f6ef19fe571effc1ca711fdb8e49f2b", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3fc57e0034e59dfc92e4a39bb7deb3238d876295a1a565b1985fb9b69ec261d2.png", - }, - { - name: "Compound", - symbol: "COMP", - address: "0xc00e94cb662c3520282e6f5717214004a7f26888", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x6bfc3cb11dcccb2a934dbad676e90523308cf1a9e07402f6850d6c1ee84d967b.png", - }, - { - name: "Movement", - symbol: "MOVE", - address: "0x3073f7aaa4db83f95e9fff17424f71d4751a3073", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x54afe78e77ea2aeac01a26c6e24da8a0d8829c4b11385df5e1b71db741c40bad.png", - }, - { - name: "ether.fi ETH", - symbol: "eETH", - address: "0x35fa164735182de50811e8e2e824cfb9b6118ac2", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x02eb37d0c375737c34d9057a6c89d563d7cb96ea30f155bcecea5040bf92a640.png", - }, -];