diff --git a/.eslintrc.json b/.eslintrc.json index 3722418..b4923b7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,11 @@ { - "extends": ["next/core-web-vitals", "next/typescript"] + "extends": ["next/core-web-vitals", "next/typescript"], + "rules": { + "react-hooks/exhaustive-deps": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "prefer-const": "off", + "@next/next/no-img-element": "off", + "react/no-unescaped-entities": "off" + } } diff --git a/app/context/network-context.tsx b/app/context/network-context.tsx index ef2e277..c3eb492 100644 --- a/app/context/network-context.tsx +++ b/app/context/network-context.tsx @@ -1,7 +1,8 @@ "use client"; -import React, { createContext, useContext, useState, ReactNode } from "react"; +import React, { createContext, useContext, useState, ReactNode, useEffect } from "react"; import { networkOptions } from "@/app/lib/constants"; +import Cookie from "js-cookie" interface NetworkContextType { currentNetwork: NetworkOption | undefined; @@ -16,6 +17,12 @@ export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ }) => { const [currentNetwork, setCurrentNetwork] = useState(); + useEffect(()=>{ + if(currentNetwork?.name){ + Cookie.set("network",currentNetwork.name) + } + },[currentNetwork?.name]) + return ( = new Map([ ["1", { id: 1, name: "Ethereum", shortName: "WETH" }], ["2", { id: 2, name: "Wrap Bitcoin", shortName: "WBTC" }], @@ -30,15 +31,44 @@ export const networkOptions = [ rpcUrl: "https://mainnet.optimism.io/", blockExplorerUrl: "https://optimistic.etherscan.io/", }, + { + id: "katana", + name: "Katana", + icon: "/katana.png", + chainId: "0xB67D2", + rpcUrl: "https://rpc.katana.network", + blockExplorerUrl: "https://explorer.katanarpc.com/", + }, ]; export const BASE_NETWORK = "base"; export const ARBITRUM_NETWORK = "arbitrum"; export const OPTIMISM_NETWORK = "optimism"; +export const KATANA_NETWORK = "katana"; export const SECS_PER_YEAR = 31556952; export const FEES = 0.01; export const oneMonthTimestampInterval = 2629743; export const referralCode = "0x0000000000000000000000000000000000000000000000000000000000000000"; -export const percentageClickValues = [10, 25, 50, 100] +export const percentageClickValues = [10, 25, 50, 100]; + +// Mock data for RemoveLiquidityTab +export const mockRemoveLiquidityState = { + percentage: 0, + token0Amount: 0, + token1Amount: 0, + fees: { + token0: 0, + token1: 0, + }, +}; + +// Mock data for FeesTab +export const mockFeesTabState = { + unclaimedFees: 0.0002, + tokens: [ + { symbol: "USDC", amount: 0.000068, usdValue: 0.000068 }, + { symbol: "USDT", amount: 0.000151, usdValue: 0.0002 }, + ], +}; diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts index e23761d..d815265 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -4,7 +4,14 @@ interface FeatureCardProps { icon: string; title: string; subtitle: string; - isSoon: boolean; + isSoon?: boolean; +} + +interface WalletItem { + name: string; + icon: string; + onClick: string | null; + label?: string; } interface NetworkOption { @@ -16,6 +23,23 @@ interface NetworkOption { blockExplorerUrl: string; } +interface PoolsType { + id: string; + name: string; + icons: string[]; + tvl: string; + tvlChange: number; + vol24h: string; + vol24hChange: number; + vol1w: string; + vol1wChange: number; + tx24h: number; + apr: string; + protocol_version: string; + swap_fee: number; + isSoon: boolean; +} + interface TabProps { tabs: { name: string; @@ -230,9 +254,50 @@ interface NavItem { count: number | null; component: React.ComponentType; // Accepts any component props?: Record; // Optional props -}; +} /* eslint-disable @typescript-eslint/no-explicit-any */ interface PositionSectionProps { dataFetching: boolean; } + +// Types for modular liquidity management tabs + +interface LiquidityTabState { + activeTab: "add" | "remove" | "fees"; +} + +interface RemoveLiquidityState { + percentage: number; + token0Amount: number; + token1Amount: number; + fees: { + token0: number; + token1: number; + }; +} + +interface FeesTabState { + unclaimedFees: number; + tokens: Array<{ + symbol: string; + amount: number; + usdValue: number; + }>; +} + +interface RemoveLiquidityTabProps { + state: RemoveLiquidityState; + onChange: (state: RemoveLiquidityState) => void; + onConnectWallet: () => void; + isWalletConnected: boolean; + token0: { symbol: string; icon: string }; + token1: { symbol: string; icon: string }; +} +interface FeesTabProps { + state: FeesTabState; + onConnectWallet: () => void; + isWalletConnected: boolean; + token0: { symbol: string; icon: string }; + token1: { symbol: string; icon: string }; +} diff --git a/app/lib/extensions.ts b/app/lib/extensions.ts new file mode 100644 index 0000000..8e9379c --- /dev/null +++ b/app/lib/extensions.ts @@ -0,0 +1,85 @@ +// Utility for detecting installed wallet extensions and returning wallet options for the modal + +interface EthereumProvider { + isMetaMask?: boolean; +} + +interface Window { + ethereum?: EthereumProvider; +} + +// Returns an array of wallet sections (Installed, Not Installed, Others) with wallet info for the modal +export const detectInstalledWallets = (): { + section: string; + items: WalletItem[]; +}[] => { + const installed: WalletItem[] = []; + const notInstalled: WalletItem[] = []; + + // Detect MetaMask extension + if ( + typeof window !== "undefined" && + (window as Window).ethereum?.isMetaMask + ) { + installed.push({ + icon: "/metamask-icon.svg", + name: "MetaMask", + label: "Installed", + onClick: "onMetaMask", + }); + } else { + notInstalled.push({ + icon: "/metamask-icon.svg", + name: "MetaMask", + label: "Not Installed", + onClick: "onMetaMask", + }); + } + + // Other wallet options (not detected, just shown as 'soon') + const others: WalletItem[] = [ + { + icon: "/coinbase-wallet-icon.svg", + name: "Coinbase", + label: "soon", + onClick: "onCoinbase", + }, + { + icon: "/trust-wallet-icon.png", + name: "Trust Wallet", + label: "soon", + onClick: "onTrustWallet", + }, + { + icon: "/wallet-connect-icon.png", + name: "Wallet Connect", + label: "soon", + onClick: "onWalletConnect", + }, + ]; + + const walletSections: { section: string; items: WalletItem[] }[] = []; + + // Add installed wallets section if any + if (installed.length > 0) { + walletSections.push({ + section: "Installed", + items: installed, + }); + } + // Add not installed wallets section if any + if (notInstalled.length > 0) { + walletSections.push({ + section: "Not Installed", + items: notInstalled, + }); + } + + // Always add others section + walletSections.push({ + section: "Others", + items: others, + }); + + return walletSections; +}; diff --git a/app/lib/helper.ts b/app/lib/helper.ts index 2b81f64..ee72e3a 100644 --- a/app/lib/helper.ts +++ b/app/lib/helper.ts @@ -69,4 +69,4 @@ export const check0xHex = (num: BigNumberish) => { export const capitalizeFirstLetter = (val: string) => { return val.charAt(0).toUpperCase() + val.slice(1); -} +}; diff --git a/app/lib/static-values.ts b/app/lib/static-values.ts index fb520f8..b8a473e 100644 --- a/app/lib/static-values.ts +++ b/app/lib/static-values.ts @@ -127,13 +127,15 @@ export const tradeMenuSubLinks = [ { title: "Perps", href: "/trade/future", - subtitle: "Leveraged perpetual contracts to trade crypto without expiration", + subtitle: + "Leveraged perpetual contracts to trade crypto without expiration", icon: "/perps-menu-icon.svg", }, { title: "Options", href: "/trade/options", - subtitle: "Leveraged contracts for hedging, speculation, or income with controlled risk", + subtitle: + "Leveraged contracts for hedging, speculation, or income with controlled risk", icon: "/options-menu-icon.svg", }, { @@ -142,6 +144,64 @@ export const tradeMenuSubLinks = [ subtitle: "Margin trade to buy/sell assets at spot prices", icon: "/spot-menu-icon.svg", }, + { + title: "Farm", + href: "/trade/farm", + subtitle: "Top Crypto Liquidity Pools Ranked by Performance", + icon: "/leaves-icon.png", + }, +]; + +export const mockPools: PoolsType[] = [ + { + id: "1", + name: "USDC / USDT", + icons: ["/usdc-icon.svg", "/usdt-icon.svg"], + tvl: "$15.98m", + tvlChange: -2.03, + vol24h: "$3.63m", + vol24hChange: 19.58, + vol1w: "$11.00m", + vol1wChange: 50.19, + tx24h: 3764, + apr: "14.47%", + protocol_version: "V3", + swap_fee: 0.01, + isSoon: false, + }, + // { + // id: "2", + // name: "AUSD / USDC", + // icons: ["/ausd-icon.svg", "/usdc-icon.svg"], + // tvl: "$12.14m", + // tvlChange: 21.12, + // vol24h: "$8.51m", + // vol24hChange: 125.9, + // vol1w: "$21.35m", + // vol1wChange: 309.61, + // tx24h: 7357, + // apr: "56.45%", + // protocol_version: "V3", + // swap_fee: 0.01, + // isSoon: true, + // }, + // { + // id: "3", + // name: "BTCK / LBTC", + // icons: ["/bitcoin.svg", "/lbtc-icon.svg"], + // tvl: "$6.32m", + // tvlChange: 253.57, + // vol24h: "$449.75", + // vol24hChange: -97.61, + // vol1w: "$258.38k", + // vol1wChange: 95.78, + // tx24h: 156, + // apr: "11.02%", + // protocol_version: "V3", + // swap_fee: 0.01, + // isSoon: true, + // }, + // Add more mock pools as needed ]; export const defaultTokenOptions: Option[] = [ @@ -163,4 +223,4 @@ export const ethPoolObj: PoolTable = { isActive: false, version: 0, vToken: "ETH", -} +}; diff --git a/app/lib/web3-constants.ts b/app/lib/web3-constants.ts index 854f068..e9e4cbe 100644 --- a/app/lib/web3-constants.ts +++ b/app/lib/web3-constants.ts @@ -12,10 +12,11 @@ export const injected = new InjectedConnector({ 901, // lyraTestNet 10, // optimism 8453, // base + 747474 ], }); -export const allowedChainIds = [8453, 42161, 10]; +export const allowedChainIds = [8453, 42161, 10, 747474]; export const getShortenedAddress = (address: string, start = 6, end = 4) => { if (address != "") { diff --git a/app/store/farm-slice.ts b/app/store/farm-slice.ts new file mode 100644 index 0000000..f5c4395 --- /dev/null +++ b/app/store/farm-slice.ts @@ -0,0 +1,36 @@ +"use client"; + +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { mockPools } from "../lib/static-values"; +import { RootState } from "../store/store"; // assuming store.ts is in same folder + +interface FarmInitalData { + FarmData: PoolsType[] | undefined; +} + +const initialState: FarmInitalData = { + FarmData: mockPools, +}; + +const FarmDataSlice = createSlice({ + name: "farm", + initialState, + reducers: { + setFarmData: (state, action: PayloadAction) => { + state.FarmData = action.payload; + }, + }, +}); + +export const { setFarmData } = FarmDataSlice.actions; + +export const selectFarmData = (state: RootState) => state.farm.FarmData; + +// Selector to get a pool by id +export const selectFarmDataById = (id: string) => (state: RootState) => { + return state.farm.FarmData?.find( + (pool: PoolsType) => String(pool.id) === String(id) + ); +}; + +export default FarmDataSlice.reducer; diff --git a/app/store/store.ts b/app/store/store.ts index 8828f58..4a033df 100644 --- a/app/store/store.ts +++ b/app/store/store.ts @@ -2,10 +2,12 @@ import { configureStore } from "@reduxjs/toolkit"; import poolsReducer from "./pools-slice"; +import farmReducer from "./farm-slice" export const store = configureStore({ reducer: { pools: poolsReducer, + farm:farmReducer }, }); diff --git a/app/trade/dashboard/page.tsx b/app/trade/dashboard/page.tsx index e223030..0758f95 100644 --- a/app/trade/dashboard/page.tsx +++ b/app/trade/dashboard/page.tsx @@ -1,5 +1,7 @@ "use client"; + + import OptionPayoffChart from "@/app/ui/dashboard/option-payoff-chart"; import { SimpleTableComponent } from "@/app/ui/dashboard/simple-table"; import { @@ -41,7 +43,7 @@ import Multicall from "@/app/abi/vanna/v1/out/Multicall.sol/Multicall.json"; import DefaultRateModel from "@/app/abi/vanna/v1/out/DefaultRateModel.sol/DefaultRateModel.json"; import Loader from "@/app/ui/components/loader"; -export default function Page() { +function Dashboard() { const { account, library } = useWeb3React(); const { currentNetwork } = useNetwork(); const [activeAccount, setActiveAccount] = useState(); @@ -1355,3 +1357,5 @@ export default function Page() { ); } + +export default Dashboard \ No newline at end of file diff --git a/app/trade/farm/[id]/page.tsx b/app/trade/farm/[id]/page.tsx new file mode 100644 index 0000000..d2b5869 --- /dev/null +++ b/app/trade/farm/[id]/page.tsx @@ -0,0 +1,347 @@ +"use client"; + +import { useParams, useRouter } from "next/navigation"; +import Image from "next/image"; +import { useDispatch, useSelector } from "react-redux"; +import { selectFarmData, selectFarmDataById } from "@/app/store/farm-slice"; +import { AppDispatch } from "@/app/store/store"; +import { useMemo, useState } from "react"; +import { motion } from "framer-motion"; +import { useWeb3React } from "@web3-react/core"; +import AddLiquidityTab from '@/app/ui/components/AddLiquidityTab'; +import RemoveLiquidityTab from '@/app/ui/components/RemoveLiquidityTab'; +import FeesTab from '@/app/ui/components/FeesTab'; +import { mockRemoveLiquidityState, mockFeesTabState } from '@/app/lib/constants'; +import { AnimatePresence } from "framer-motion"; + +// Interface for price range management +interface priceType { + minPrice: number; + maxPrice: number; +} + +/** + * Farm Detail Page Component + * Displays detailed information about a specific liquidity farming pool + * Allows users to add liquidity with custom price ranges + */ +export default function PoolDetailPage({ params }: { params: { id: string } }) { + // Navigation and routing hooks + const router = useRouter(); + const { id } = params; + + // Web3 connection state + const { account } = useWeb3React(); + + // Redux state management + const pool = useSelector(selectFarmDataById(id)); + const dispatch = useDispatch(); + + // Price range state for liquidity position + const [prices, setPrices] = useState({ + minPrice: 0.9981, + maxPrice: 1.0001, + }); + + // Token input amounts and USD price states + const [token0Amount, setToken0Amount] = useState(""); + const [token1Amount, setToken1Amount] = useState(""); + const [usdcUsdPrice, setUsdcUsdPrice] = useState("0.00"); + const [usdtUsdPrice, setUsdtUsdPrice] = useState("0.00"); + + // Tab state + const [activeTab, setActiveTab] = useState<'add' | 'remove' | 'fees'>('add'); + + // Remove liquidity state + const [removeState, setRemoveState] = useState(mockRemoveLiquidityState); + // Fees tab state + const [feesState, setFeesState] = useState(mockFeesTabState); + + /** + * Parse pool data to extract token information + * Creates token objects with symbols, icons, addresses, and balances + */ + const [token0, token1] = useMemo(() => { + // Split pool name to get individual token symbols (e.g., "USDC / USDT") + const [symbol0, symbol1] = pool?.name?.split("/")?.map((s: string) => s.trim()) || [ + "USDC", + "USDT", + ]; + const icons = pool?.icons || []; + + return [ + { + symbol: symbol0, + icon: icons[0] || "/usdc-icon.svg", + address: "0x203A...FD36", + balance: 0, + value: 0, + }, + { + symbol: symbol1, + icon: icons[1] || "/usdt-icon.svg", + address: "0x2DCa...D2F2", + balance: 0, + value: 0, + }, + ]; + }, [pool?.name, pool?.icons]); + + // Early return if pool data is not found + if (!pool) { + return ( + +
Pool not found.
+
+ ); + } + + return ( + +
+ {/* Navigation: Back button */} + router.back()} + initial={{ opacity: 0, x: -20 }} + animate={{ opacity: 1, x: 0 }} + transition={{ duration: 0.4, delay: 0.1 }} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + Back + + + {/* Pool Header Section */} + + {/* Token icons, pool name, and protocol version badge */} +
+ {/* Token pair icons with overlapping design */} + +
+ {token0.symbol} +
+
+ {token1.symbol} +
+
+ + {/* Pool name */} + + {pool.name} + + + {/* Protocol version badge */} + + {pool.protocol_version} + +
+ + {/* Pool statistics and information */} +
+
+ APR{" "} + + {pool.apr} + +
+
+ Fee{" "} + + {pool.swap_fee}% + +
+
+ Network{" "} + + Katana + +
+
+ {token0.symbol}{" "} + + {token0.address} + +
+
+ {token1.symbol}{" "} + + {token1.address} + +
+
+
+ + {/* Main Content Card */} + +
+ {/* Tab Switcher */} +
+ {/* Add Tab */} + {activeTab === 'add' ? ( +
+ +
+ ) : ( + + )} + {/* Remove Tab */} + {activeTab === 'remove' ? ( +
+ +
+ ) : ( + + )} + {/* Fees Tab */} + {activeTab === 'fees' ? ( +
+ +
+ ) : ( + + )} +
+ {/* Tab Content */} + + {activeTab === 'add' && ( + + + + )} + {activeTab === 'remove' && ( + + { }} + isWalletConnected={!!account} + token0={token0} + token1={token1} + /> + + )} + {activeTab === 'fees' && ( + + { }} + isWalletConnected={!!account} + token0={token0} + token1={token1} + /> + + )} + +
+
+
+
+ ); +} diff --git a/app/trade/farm/page.tsx b/app/trade/farm/page.tsx new file mode 100644 index 0000000..e454d14 --- /dev/null +++ b/app/trade/farm/page.tsx @@ -0,0 +1,241 @@ +"use client"; + +import Image from "next/image"; +import { useDispatch, useSelector } from "react-redux"; +import { useRouter } from "next/navigation"; +import { selectFarmData } from "@/app/store/farm-slice"; +import { AppDispatch } from "@/app/store/store"; +import { motion } from "framer-motion"; + +/** + * ChangeText Component + * Displays percentage changes with color-coded styling and animation + * Green for positive changes, red for negative, gray for zero + */ +function ChangeText({ value }: { value: number }) { + return ( + 0 + ? "text-green-400" + : value < 0 + ? "text-red-400" + : "text-gray-400" + } + initial={{ opacity: 0, scale: 0.8 }} + animate={{ opacity: 1, scale: 1 }} + transition={{ duration: 0.3 }} + > + {value > 0 ? "+" : ""} + {value}% + + ); +} + +/** + * Farm Page Component + * Displays a table of farming pools with their statistics + * Includes animations, hover effects, and navigation to individual pool details + */ +export default function Page() { + // Router for navigation to individual pool pages + const router = useRouter(); + + // Redux state management - get farm data from store + const poolData = useSelector(selectFarmData); + const dispatch = useDispatch(); + + // Animation variants for staggered table row animations + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, // Stagger child animations by 0.1s + delayChildren: 0.2, // Delay start of child animations by 0.2s + }, + }, + }; + + return ( + // Main container with fade-in animation + +
+ {/* Page title with slide-down animation */} + + Farm + + + {/* Main table container with scale animation */} + + + {/* Table header with column titles */} + + + + + + + + + + + + {/* Table body with staggered row animations */} + + {/* Map through pool data to create table rows */} + {poolData?.map((pool, index) => ( + { + // Prevent navigation for pools marked as "coming soon" + if (pool.isSoon) return; + // Navigate to individual pool page + router.push(`/trade/farm/${pool.id}`); + }} + whileHover={{ + transition: { duration: 0.2 }, + }} + whileTap={{ scale: 0.98 }} // Scale down on tap for feedback + > + {/* Gradient hover effect overlay */} +
+
+
+
+ + {/* Pool name and icons column */} +
+ + {/* TVL (Total Value Locked) column with change indicator */} + + + {/* 24h Volume column with change indicator */} + + + {/* 1 week Volume column with change indicator */} + + + {/* 24h Transactions column */} + + + {/* APR (Annual Percentage Rate) column */} + + + ))} + +
NameTVL + Volume (24h) + + Volume (1w) + + Transactions (24h) + APR
+ {/* Dual token icons with hover animation */} + + {/* First token icon */} +
+ {pool.name} +
+ {/* Second token icon (overlapping) */} +
+ {pool.name} +
+
+ + {/* Pool name and metadata */} +
+
+
{pool.name}
+
+ +
+
+
+ {/* Protocol version badge */} + + {pool.protocol_version} + + {/* Swap fee badge */} + + {pool.swap_fee}% + + {/* "Coming soon" badge for pools not yet available */} + {pool.isSoon && ( + + + + )} +
+
+
+
{pool.tvl}
+ +
+
{pool.vol24h}
+ +
+
{pool.vol1w}
+ +
+ {pool.tx24h} + + {pool.apr} +
+
+
+
+ ); +} diff --git a/app/trade/future/page.tsx b/app/trade/future/page.tsx index 08a26f2..18fb6a1 100644 --- a/app/trade/future/page.tsx +++ b/app/trade/future/page.tsx @@ -19,7 +19,8 @@ import OpMarkPrice from "../../abi/vanna/v1/out/OpMarkPrice.sol/OpMarkPrice.json import OpIndexPrice from "../../abi/vanna/v1/out/OpIndexPrice.sol/OpIndexPrice.json"; import { ceilWithPrecision } from "@/app/lib/helper"; -export default function Page() { + +function Future() { const { library } = useWeb3React(); const { currentNetwork } = useNetwork(); @@ -281,3 +282,6 @@ export default function Page() { ); } + + +export default Future diff --git a/app/trade/options/page.tsx b/app/trade/options/page.tsx index 2030cfb..f88044f 100644 --- a/app/trade/options/page.tsx +++ b/app/trade/options/page.tsx @@ -26,7 +26,7 @@ type DateOption = | "Next month"; type GreekOption = "Delta" | "Mark Price" | "Gamma" | "Vega" | "Theta"; -export default function Page() { +function Options() { // const { account, library } = useWeb3React(); // const { currentNetwork } = useNetwork(); @@ -558,3 +558,6 @@ export default function Page() { ); } + + +export default Options \ No newline at end of file diff --git a/app/trade/spot/page.tsx b/app/trade/spot/page.tsx index 5a6ecff..f468728 100644 --- a/app/trade/spot/page.tsx +++ b/app/trade/spot/page.tsx @@ -42,7 +42,7 @@ import { formatUSD } from "@/app/lib/number-format-helper"; import CreateSmartAccountModal from "@/app/ui/components/create-smart-account-model"; import Notification from "@/app/ui/components/notification"; -export default function Page() { +function Spot() { const { account, library } = useWeb3React(); const [activeAccount, setActiveAccount] = useState(); const { currentNetwork } = useNetwork(); @@ -867,3 +867,6 @@ export default function Page() { ); } + + +export default Spot \ No newline at end of file diff --git a/app/ui/components/AddLiquidityTab.tsx b/app/ui/components/AddLiquidityTab.tsx new file mode 100644 index 0000000..27a3c19 --- /dev/null +++ b/app/ui/components/AddLiquidityTab.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import Image from 'next/image'; +import { motion } from 'framer-motion'; + +export interface AddLiquidityTabProps { + prices: { minPrice: number; maxPrice: number }; + setPrices: (prices: { minPrice: number; maxPrice: number }) => void; + token0Amount: string; + setToken0Amount: (amount: string) => void; + token1Amount: string; + setToken1Amount: (amount: string) => void; + usdcUsdPrice: string; + usdtUsdPrice: string; + account: string | null | undefined; + token0: { symbol: string; icon: string; address: string }; + token1: { symbol: string; icon: string; address: string }; +} + +const AddLiquidityTab: React.FC = ({ + prices, + setPrices, + token0Amount, + setToken0Amount, + token1Amount, + setToken1Amount, + usdcUsdPrice, + usdtUsdPrice, + account, + token0, + token1, +}) => { + return ( + <> + {/* Price Range Controls */} + + {/* Minimum Price Control */} + +
Min Price
+
{prices.minPrice.toFixed(4)}
+
{token1.symbol} per {token0.symbol}
+
+ setPrices({ ...prices, minPrice: prices.minPrice - 0.0001 })}>- + setPrices({ ...prices, minPrice: prices.minPrice + 0.0001 })}>+ +
+
+ {/* Maximum Price Control */} + +
Max Price
+
{prices.maxPrice.toFixed(4)}
+
{token1.symbol} per {token0.symbol}
+
+ setPrices({ ...prices, maxPrice: prices.maxPrice - 0.0001 })}>- + setPrices({ ...prices, maxPrice: prices.maxPrice + 0.0001 })}>+ +
+
+
+ {/* Liquidity Information Section */} + +
Liquidity
+
Depending on your range, the supplied tokens for this position will not always be a 50:50 ratio.
+
+ {/* Token Input Section */} + + {/* First Token Input */} + +
+ {token0.symbol} +
+
+ setToken0Amount(e.target.value)} type="number" className="no-spinner bg-transparent focus:outline-none text-lg font-bold text-baseBlack dark:text-white w-full" /> +
${usdcUsdPrice}
+
+
+
+ {token0.symbol} + {token0.symbol} +
+
+ + + +
$0.00
+
+
+
+ {/* Second Token Input */} + +
+ {token1.symbol} +
+
+ setToken1Amount(e.target.value)} type="number" className="no-spinner bg-transparent focus:outline-none text-lg font-bold text-baseBlack dark:text-white w-full" /> +
${usdtUsdPrice}
+
+
+
+ {token1.symbol} + {token1.symbol} +
+
+ + + +
$0.00
+
+
+
+
+ {/* Action Button Section */} + + {!account && ( + Connect Wallet + )} + {account && !token0Amount && ( + Enter an amount + )} + {account && token0Amount && ( + Add Liquidity + )} + + + ); +}; + +export default AddLiquidityTab; \ No newline at end of file diff --git a/app/ui/components/FeesTab.tsx b/app/ui/components/FeesTab.tsx new file mode 100644 index 0000000..ce9cbf7 --- /dev/null +++ b/app/ui/components/FeesTab.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import Image from 'next/image'; + +const FeesTab: React.FC = ({ + state, + onConnectWallet, + isWalletConnected, + token0, + token1, +}) => { + return ( +
+
+
Unclaimed fees
+
${state.unclaimedFees.toFixed(4)}
+
+
+
Tokens
+
+ {state.tokens.map((token, idx) => ( +
+ {token.symbol} + {token.symbol} + {token.amount} + ${token.usdValue} +
+ ))} +
+
+ {!isWalletConnected && ( + + )} + {isWalletConnected && ( + + )} +
+ ); +}; + +export default FeesTab; \ No newline at end of file diff --git a/app/ui/components/ModalWalletInfoSection.tsx b/app/ui/components/ModalWalletInfoSection.tsx new file mode 100644 index 0000000..574669c --- /dev/null +++ b/app/ui/components/ModalWalletInfoSection.tsx @@ -0,0 +1,103 @@ +import React from "react"; + +// Info section for the wallet connect modal, explaining what a wallet is and providing helpful actions +export default function ModalWalletInfoSection({ + onGetWallet, // Called when the user clicks 'Get a Wallet' + onLearnMore, // Called when the user clicks 'Learn More' + onBack, // Called when the user clicks the back arrow + onClose, // Called when the user clicks the close (X) button +}: { + onGetWallet?: () => void; + onLearnMore?: () => void; + onBack?: () => void; + onClose?: () => void; +}) { + return ( +
+ {/* Header with back and close buttons */} +
+ +

+ What is a Wallet? +

+ +
+ {/* Info Cards: Explain wallets and login */} +
+
+
+ {/* Wallet icon */} + + 💼 + +
+
+
+ A Home for your Digital Assets +
+
+ Wallets are used to send, receive, store, and display digital + assets like Ethereum and NFTs. +
+
+
+
+
+ {/* Login icon */} + + 🔑 + +
+
+
+ A New Way to Log In +
+
+ Instead of creating new accounts and passwords on every website, + just connect your wallet. +
+
+
+
+ {/* Action buttons */} + + +
+ ); +} diff --git a/app/ui/components/ModalWalletsSection.tsx b/app/ui/components/ModalWalletsSection.tsx new file mode 100644 index 0000000..3161d98 --- /dev/null +++ b/app/ui/components/ModalWalletsSection.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import { motion } from "framer-motion"; + +// Represents a single wallet option +interface WalletItem { + name: string; + icon: string; + onClick?: string | null; + label?: string; +} + +// Renders the wallet selection section of the modal, including all wallet options and the footer +export default function ModalWalletsSection({ + wallets, // Array of wallet sections (Installed, Not Installed, Others) + onMetaMask, // Handler for MetaMask connect + onClose, // Handler for closing the modal +}: { + wallets: { section: string; items: WalletItem[] }[] | null; + onMetaMask: () => void; + onClose: () => void; +}) { + return ( + <> + {/* Wallet sections list */} +
+ {wallets?.map((section, idx) => ( +
+
+ {section.section} +
+
+ {section.items.map((wallet, widx) => ( + + {/* Gradient hover effect overlay */} + + + + + {/* Button content */} + + {wallet.name} + {wallet.name} + {wallet.label && ( + + {wallet.label} + + )} + + + ))} +
+ {idx < wallets.length - 1 && ( +
+ )} +
+ ))} +
+ {/* Footer with terms and privacy links */} +
+ By connecting your wallet, you agree to Vanna's{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + +
+ + ); +} diff --git a/app/ui/components/RemoveLiquidityTab.tsx b/app/ui/components/RemoveLiquidityTab.tsx new file mode 100644 index 0000000..a544c56 --- /dev/null +++ b/app/ui/components/RemoveLiquidityTab.tsx @@ -0,0 +1,147 @@ +import React, { useState } from "react"; +import Image from "next/image"; +import FarmSlider from "../future/farmSlider"; + +const percentageOptions = [25, 50, 75, 100]; + +const RemoveLiquidityTab: React.FC = ({ + state, + onChange, + onConnectWallet, + isWalletConnected, + token0, + token1, +}) => { + const handlePercentageChange = (value: number) => { + onChange({ + ...state, + percentage: value, + // Optionally update token amounts here if needed + }); + }; + + return ( +
+
+
+ Remove liquidity +
+
+ Please enter how much of the position you want to remove. +
+
+
+
+ {state.percentage}% +
+ +
+ {percentageOptions.map((opt) => ( + + ))} +
+
+
+ handlePercentageChange(val)} + value={state.percentage} + key={"range"} + /> +
+
+
+
+
+
+ You'll receive +
+
+
+ {token0.symbol} + {token0.symbol} + + {state.token0Amount} + +
+
+ {token1.symbol} + {token1.symbol} + + {state.token1Amount} + +
+
+
+
+
+
+ You'll receive collected fees +
+
+
+ {token0.symbol} + {token0.symbol} + + {state.fees.token0} + +
+
+ {token1.symbol} + {token1.symbol} + + {state.fees.token1} + +
+
+
+
+
+ + {!isWalletConnected && ( + + )} + {isWalletConnected && ( + + )} +
+ ); +}; + +export default RemoveLiquidityTab; diff --git a/app/ui/components/WalletConnectModal.tsx b/app/ui/components/WalletConnectModal.tsx new file mode 100644 index 0000000..175fa8f --- /dev/null +++ b/app/ui/components/WalletConnectModal.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { detectInstalledWallets } from "@/app/lib/extensions"; +import ModalWalletInfoSection from "./ModalWalletInfoSection"; +import ModalWalletsSection from "./ModalWalletsSection"; + +// Main modal for connecting a wallet. Handles info/help section and wallet selection section. +interface WalletConnectModalArgs { + open: boolean; // Whether the modal is open + onClose: () => void; // Handler to close the modal + onMetaMask: () => void; // Handler for MetaMask connect + // You can add more wallet handlers as needed +} + +export default function WalletConnectModal({ + open, + onClose, + onMetaMask, +}: WalletConnectModalArgs) { + // Wallet sections (Installed, Not Installed, Others) + const [wallets, setWallets] = useState< + { section: string; items: WalletItem[] }[] | null + >(null); + // Whether to show the info/help section + const [showInfo, setShowInfo] = useState(false); + + // Detect installed wallets on mount + useEffect(() => { + const wallets = detectInstalledWallets(); + setWallets(wallets); + }, []); + + return ( + + {open && ( +
+ + {/* Show info section or wallets section */} + {showInfo ? ( + // Info/help section ("What is a Wallet?") + + window.open("https://metamask.io/download.html", "_blank") + } + onLearnMore={() => + window.open("https://ethereum.org/en/wallets/", "_blank") + } + onBack={() => setShowInfo(false)} + onClose={() => { + setShowInfo(false); + onClose(); + }} + /> + ) : ( + <> + {/* Header with help and close buttons */} +
+ +

+ Connect a Wallet +

+ +
+ {/* Wallet selection section */} + + + )} +
+
+ )} +
+ ); +} diff --git a/app/ui/future/farmSlider.tsx b/app/ui/future/farmSlider.tsx new file mode 100644 index 0000000..163b789 --- /dev/null +++ b/app/ui/future/farmSlider.tsx @@ -0,0 +1,109 @@ +"use client"; +import Image from "next/image"; +import React, { useEffect, useState } from "react"; + +interface SliderProps { + value: number; + onChange: (val: number) => void; +} + +const FarmSlider: React.FC = ({ value, onChange }) => { + const [inputValue, setInputValue] = useState(value.toString()); + + useEffect(() => { + setInputValue(value.toString()); + }, [value]); + + const handleSliderChange = (e: React.ChangeEvent) => { + const newValue = Number(e.target.value); + onChange(newValue); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + setInputValue(newValue); + const numValue = Number(newValue); + if (!isNaN(numValue) && numValue >= 0 && numValue <= 100) { + onChange(numValue); + } + }; + + const handleInputBlur = () => { + let numValue = Number(inputValue); + if (isNaN(numValue) || numValue < 0) { + numValue = 0; + } else if (numValue > 100) { + numValue = 100; + } + const rounded = Math.round(numValue); + setInputValue(rounded.toString()); + onChange(rounded); + }; + + return ( +
+
+ {/* Slider */} +
+ {/* Background track */} +
+ + {/* Gradient track */} +
+ + {/* Hexagon thumb */} +
+
+
+ ^_^ +
+
+
+ + {/* Invisible range input */} + + + {/* Optional Tick Marks */} +
+ {[0, 25, 50, 75, 100].map((val, i) => ( + {val}% + ))} +
+
+
+
+ ); +}; + +export default FarmSlider; diff --git a/app/ui/header/burger-menu.tsx b/app/ui/header/burger-menu.tsx index 32cf0fc..7ecc15a 100644 --- a/app/ui/header/burger-menu.tsx +++ b/app/ui/header/burger-menu.tsx @@ -4,7 +4,7 @@ import { CaretDown, CaretUp } from "@phosphor-icons/react"; import { menuLinks, tradeMenuSubLinks } from "@/app/lib/static-values"; import Image from "next/image"; -export default function BurgerMenu({ onClose }: BurgerMenuProps) { +export default function BurgerMenu({ onClose, onConnectWallet }: BurgerMenuProps & { onConnectWallet?: () => void }) { const [isTradeExpanded, setIsTradeExpanded] = useState(false); return ( @@ -58,6 +58,15 @@ export default function BurgerMenu({ onClose }: BurgerMenuProps) {
))} + {/* Connect Wallet button at the bottom */} +
+ +
); diff --git a/app/ui/header/feature-card.tsx b/app/ui/header/feature-card.tsx index 57e0296..89a7fd4 100644 --- a/app/ui/header/feature-card.tsx +++ b/app/ui/header/feature-card.tsx @@ -8,6 +8,7 @@ const FeatureCard: React.FC = ({ subtitle, isSoon, }) => { + return (
@@ -50,21 +52,32 @@ export default function NavLinks() { key={link.title + "2"} className="absolute left-2 top-10 z-50 mt-2 rounded-md shadow-xl bg-white dark:bg-baseDark ring-1 ring-black dark:ring-neutral-800 ring-opacity-5 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300" > - {tradeMenuSubLinks.map((subItem, index) => ( - - - - ))} + {tradeMenuSubLinks + .filter((subItem) => { + // On Katana: only allow "Farm" + if (currentNetwork?.name === "Katana") { + return subItem.title === "Farm"; + } else { + return subItem.title !=="Farm" + } + // On other networks: allow all + return true; + }) + .map((subItem, index) => ( + + + + ))}
); diff --git a/app/ui/header/navbar-button.tsx b/app/ui/header/navbar-button.tsx index 70cfa63..a0f417e 100644 --- a/app/ui/header/navbar-button.tsx +++ b/app/ui/header/navbar-button.tsx @@ -19,6 +19,7 @@ import { ethers } from "ethers"; import { opAddressList } from "@/app/lib/web3-constants"; import Faucet from "../../abi/vanna/v1/out/Faucet.sol/Faucet.json"; import Loader from "../components/loader"; +import WalletConnectModal from "../components/WalletConnectModal"; declare global { interface Window { @@ -34,6 +35,7 @@ export default function NavbarButtons() { const { account, activate, deactivate, chainId, library } = useWeb3React(); const [disable, setDisable] = useState(true); const [loading, setLoading] = useState(false); + const [walletModalOpen, setWalletModalOpen] = useState(false); const walletConnect = useCallback(async () => { try { @@ -263,9 +265,7 @@ export default function NavbarButtons() { {!account && ( @@ -281,6 +281,15 @@ export default function NavbarButtons() { )} + setWalletModalOpen(false)} + onMetaMask={() => { + setWalletModalOpen(false); + walletConnect(); + }} + /> +
{notifications.map(({ id, type, message }) => ( { return; } } - setSelectedNetwork(network); setCurrentNetwork(network); }; @@ -69,8 +68,11 @@ export const NetworkDropdown = () => { } else if (chainId === 10) { setSelectedNetwork(networks[2]); setCurrentNetwork(networks[2]); + } else if (chainId === 747474) { + setSelectedNetwork(networks[3]); + setCurrentNetwork(networks[3]); } else { - switchNetwork(networks[0]); + switchNetwork(networks[0]) } } diff --git a/middleware.ts b/middleware.ts index ef144c4..bd7ab45 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { isTokenValid } from "./app/lib/access-utils"; +import Cookie from 'js-cookie' export function middleware(request: NextRequest) { // Allow direct access to SVG files in the root directory @@ -17,6 +18,21 @@ export function middleware(request: NextRequest) { return NextResponse.redirect(new URL("/access", request.url)); } + const network = request.cookies.get("network")?.value + const isTradeurl = request.nextUrl.pathname.startsWith("/trade") + const isFarmPage = request.nextUrl.pathname === "/trade/farm" + const isFarmDetailedPage = request.nextUrl.pathname.startsWith("/trade/farm/") + + if(network==="Katana" && isTradeurl && !isFarmPage && !isFarmDetailedPage){ + const url = request.nextUrl.clone() + url.pathname = "/" + return NextResponse.redirect(url) + } else if (network!=="Katana" && isTradeurl && (isFarmPage || isFarmDetailedPage)) { + const url = request.nextUrl.clone() + url.pathname = "/" + return NextResponse.redirect(url) + } + return NextResponse.next(); } diff --git a/next.config.mjs b/next.config.mjs index 4678774..5f27680 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,15 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default nextConfig; +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'source.unsplash.com', + pathname: '/random/**', + }, + ], + }, + }; + + export default nextConfig; + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 749f6e8..6cd5bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@web3-react/injected-connector": "^6.0.7", "axios": "^1.7.7", "ethers": "^5.7.2", + "framer-motion": "^12.23.6", + "js-cookie": "^3.0.5", "next": "14.2.8", "react": "^18", "react-dom": "^18", @@ -24,6 +26,7 @@ }, "devDependencies": { "@metamask/providers": "^18.2.0", + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -1614,6 +1617,13 @@ "@types/ms": "*" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3945,6 +3955,33 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.6.tgz", + "integrity": "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.6", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4892,6 +4929,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -5173,6 +5219,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.6.tgz", + "integrity": "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 6ed43c6..5bd77bb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "@web3-react/injected-connector": "^6.0.7", "axios": "^1.7.7", "ethers": "^5.7.2", + "framer-motion": "^12.23.6", + "js-cookie": "^3.0.5", "next": "14.2.8", "react": "^18", "react-dom": "^18", @@ -25,6 +27,7 @@ }, "devDependencies": { "@metamask/providers": "^18.2.0", + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/public/acala-ausd-seeklogo.svg b/public/acala-ausd-seeklogo.svg new file mode 100644 index 0000000..7f05f87 --- /dev/null +++ b/public/acala-ausd-seeklogo.svg @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/bitcoin.svg b/public/bitcoin.svg new file mode 100644 index 0000000..cae4d6a --- /dev/null +++ b/public/bitcoin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/coinbase-wallet-icon.svg b/public/coinbase-wallet-icon.svg new file mode 100644 index 0000000..c47fdf9 --- /dev/null +++ b/public/coinbase-wallet-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/katana.png b/public/katana.png new file mode 100644 index 0000000..8dafb75 Binary files /dev/null and b/public/katana.png differ diff --git a/public/lbtc-icon.svg b/public/lbtc-icon.svg new file mode 100644 index 0000000..1fed0b1 --- /dev/null +++ b/public/lbtc-icon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/lbtc-icon.webp b/public/lbtc-icon.webp new file mode 100644 index 0000000..a35837e Binary files /dev/null and b/public/lbtc-icon.webp differ diff --git a/public/leaves-icon.png b/public/leaves-icon.png new file mode 100644 index 0000000..525c42d Binary files /dev/null and b/public/leaves-icon.png differ diff --git a/public/leaves-icon.svg b/public/leaves-icon.svg new file mode 100644 index 0000000..b7ec676 --- /dev/null +++ b/public/leaves-icon.svg @@ -0,0 +1 @@ +leaves \ No newline at end of file diff --git a/public/metamask-icon.svg b/public/metamask-icon.svg new file mode 100644 index 0000000..003b12e --- /dev/null +++ b/public/metamask-icon.svg @@ -0,0 +1 @@ +metamask \ No newline at end of file diff --git a/public/trust-wallet-icon.png b/public/trust-wallet-icon.png new file mode 100644 index 0000000..f9a7210 Binary files /dev/null and b/public/trust-wallet-icon.png differ diff --git a/public/trust-wallet-icon.svg b/public/trust-wallet-icon.svg new file mode 100644 index 0000000..f9a7210 Binary files /dev/null and b/public/trust-wallet-icon.svg differ diff --git a/public/wallet-connect-icon.png b/public/wallet-connect-icon.png new file mode 100644 index 0000000..24a9d88 Binary files /dev/null and b/public/wallet-connect-icon.png differ diff --git a/public/wallet-connect.svg b/public/wallet-connect.svg new file mode 100644 index 0000000..c74cd6e Binary files /dev/null and b/public/wallet-connect.svg differ