├── .nvmrc ├── app ├── hooks │ ├── useSidebar.ts │ ├── index.ts │ ├── useWallets.ts │ ├── useWalletContext.ts │ ├── useAboutGasStationModal.tsx │ ├── useStableSwitchWallet.ts │ ├── useLinkedWallets.ts │ ├── useWalletClient.ts │ ├── useOnSetPrimaryWallet.ts │ ├── useAdaptedWallet.ts │ └── useOnLinkNewWallet.ts ├── mint-teth │ ├── constants │ │ ├── index.ts │ │ ├── contracts.ts │ │ └── tokens.ts │ ├── abis │ │ ├── GasRouter.json │ │ └── WarpRouteContract.json │ ├── lib │ │ ├── quoteGasPayment.ts │ │ ├── getRateInQuote.ts │ │ ├── latestRoundData.ts │ │ ├── warpcore.ts │ │ ├── getRate.ts │ │ ├── balanceOf.ts │ │ └── userAtomicRequest.ts │ ├── utils │ │ ├── calculateMinimumMint.ts │ │ ├── getSolanaBalance.ts │ │ └── sanitizeInput.ts │ ├── components │ │ ├── Tooltip.tsx │ │ ├── MintSummaryCard.tsx │ │ ├── MintAndRedeem.tsx │ │ ├── RedeemSummaryCard.tsx │ │ └── EcSelect.tsx │ ├── types │ │ └── index.ts │ └── page.tsx ├── mint-tusd │ ├── constants │ │ ├── index.ts │ │ ├── contracts.ts │ │ └── tokens.ts │ ├── abis │ │ ├── GasRouter.json │ │ └── WarpRouteContract.json │ ├── lib │ │ ├── quoteGasPayment.ts │ │ ├── getRateInQuote.ts │ │ ├── latestRoundData.ts │ │ ├── warpcore.ts │ │ ├── getRate.ts │ │ ├── balanceOf.ts │ │ └── userAtomicRequest.ts │ ├── utils │ │ ├── calculateMinimumMint.ts │ │ ├── getSolanaBalance.ts │ │ └── sanitizeInput.ts │ ├── components │ │ ├── Tooltip.tsx │ │ ├── MintSummaryCard.tsx │ │ ├── MintAndRedeem.tsx │ │ ├── RedeemSummaryCard.tsx │ │ └── EcSelect.tsx │ ├── types │ │ └── index.ts │ └── page.tsx ├── bridge │ └── page.tsx ├── instrumentation.ts ├── components │ ├── icons │ │ ├── arrow.tsx │ │ ├── chevron-right-small.tsx │ │ ├── sidebar-icons │ │ │ ├── bridge.tsx │ │ │ ├── faucet.tsx │ │ │ ├── gas-station.tsx │ │ │ ├── wallet.tsx │ │ │ ├── ellipse.tsx │ │ │ └── scan.tsx │ │ ├── activity.tsx │ │ ├── instant-icon.tsx │ │ ├── thirdparty-bridges-icon.tsx │ │ ├── disconnect.tsx │ │ ├── arrow-up-right.tsx │ │ ├── warning-icon.tsx │ │ ├── block.tsx │ │ ├── gas.tsx │ │ ├── connect.tsx │ │ ├── eth.tsx │ │ ├── circle-check.tsx │ │ ├── loading.tsx │ │ ├── square-arrow-top-right.tsx │ │ ├── circle-info.tsx │ │ ├── transferArrow.tsx │ │ ├── copy.tsx │ │ ├── index.js │ │ └── cross.tsx │ ├── constants │ │ └── index.ts │ ├── deprecated │ │ └── ExtendedDetails │ │ │ └── styles.css │ ├── ExtendedDetails │ │ └── styles.css │ ├── WarningComponent.tsx │ ├── TransactionPool │ │ └── types.ts │ ├── Header │ │ └── index.tsx │ ├── Deposit │ │ ├── NetworkSwitcher.css │ │ ├── RelaySwapWidget.tsx │ │ ├── AirdropPopup.tsx │ │ ├── LrtPopup.tsx │ │ ├── activity.css │ │ └── TapPopup.tsx │ ├── ErrorBoundary.tsx │ ├── ThirdpartyBridgeModal │ │ ├── constants.ts │ │ ├── ThirdpartyBridgeItem.tsx │ │ └── ThirdpartyBridgeModalContext.tsx │ ├── Sidebar │ │ └── sidebar.css │ ├── GasStation │ │ ├── BridgeRedirectionComponent.tsx │ │ ├── SelectToken.tsx │ │ └── AboutGasStation.tsx │ └── Footer │ │ └── index.tsx ├── global-error.tsx ├── instrumentation-client.ts ├── sentry-example-page │ └── page.tsx ├── providers.tsx ├── providers │ ├── GasProviders.tsx │ ├── WalletFilterProvider.tsx │ ├── providers.tsx │ ├── RelayKitProvider.tsx │ └── wagmiProvider.tsx ├── context.tsx ├── contexts │ └── SidebarContext.tsx ├── layout.tsx ├── api │ ├── get-transactions │ │ └── route.ts │ └── ethereum-data │ │ └── route.ts ├── client-layout.tsx ├── gas-station │ └── page.tsx └── page.tsx ├── .github ├── CODEOWNERS └── dependabot.yml ├── .eslintrc.json ├── public ├── cow.png ├── eth.png ├── logo.png ├── swap.png ├── clouds.png ├── grass.png ├── eclipse-e.png ├── eclipse.png ├── tos-image.png ├── wordmark.png ├── token-tusd.png ├── es-token-logo.png ├── twitter-banner.png ├── unknown-token.png ├── eclipse-favicon.png ├── eclipse-testnet.png ├── gas-station-info.png ├── testnet-favicon.png ├── airdrop-background.png ├── instant-swap-icon.png ├── passive-grass-banner.png ├── passive-grass-plus-one.png ├── thirdparty-bridges │ ├── owlto.png │ ├── relay.png │ ├── gaszip.png │ ├── orbiter.png │ ├── stride.png │ ├── alldomains.png │ ├── hyperlane.png │ ├── minibridge.png │ └── retrobridge.png ├── fonts │ └── mikado │ │ ├── HvDTrial_Mikado-Black.otf │ │ ├── HvDTrial_Mikado-Bold.otf │ │ ├── HvDTrial_Mikado-Light.otf │ │ ├── HvDTrial_Mikado-Ultra.otf │ │ ├── HvDTrial_Mikado-Medium.otf │ │ ├── HvDTrial_Mikado-Regular.otf │ │ ├── HvDTrial_Mikado-BoldItalic.otf │ │ ├── HvDTrial_Mikado-BlackItalic.otf │ │ ├── HvDTrial_Mikado-LightItalic.otf │ │ ├── HvDTrial_Mikado-MediumItalic.otf │ │ ├── HvDTrial_Mikado-UltraItalic.otf │ │ └── HvDTrial_Mikado-RegularItalic.otf ├── chevron-right-small.svg ├── clock.svg ├── info.svg ├── token-rsweth.svg ├── vercel.svg ├── wallet.svg ├── gas.svg ├── token-ezeth.svg ├── next.svg ├── token-usdc.svg └── token-weeth.svg ├── lib ├── typeUtils.ts ├── networkUtils.ts ├── isDynamicEclipseNetworkId.ts ├── stringUtils.ts ├── classnameUtils.ts ├── walletClient.ts ├── octaneUtils.ts ├── priceUtils.ts ├── solanaUtils.ts ├── relay.ts └── ethUtils.ts ├── postcss.config.js ├── .env.example ├── tailwind.config.ts ├── .gitignore ├── tsconfig.json ├── README.md ├── next.config.js └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /app/hooks/useSidebar.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dynamic-labs/developers -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/mint-teth/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const hyperlaneIdForEclipse = 1408864445; 2 | -------------------------------------------------------------------------------- /app/mint-tusd/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const hyperlaneIdForEclipse = 1408864445; 2 | -------------------------------------------------------------------------------- /public/cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/cow.png -------------------------------------------------------------------------------- /public/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/eth.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/swap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/swap.png -------------------------------------------------------------------------------- /public/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/clouds.png -------------------------------------------------------------------------------- /public/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/grass.png -------------------------------------------------------------------------------- /public/eclipse-e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/eclipse-e.png -------------------------------------------------------------------------------- /public/eclipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/eclipse.png -------------------------------------------------------------------------------- /public/tos-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/tos-image.png -------------------------------------------------------------------------------- /public/wordmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/wordmark.png -------------------------------------------------------------------------------- /app/bridge/page.tsx: -------------------------------------------------------------------------------- 1 | import Main from "../page" 2 | 3 | export default function Bridge() { 4 | return
; 5 | } 6 | -------------------------------------------------------------------------------- /public/token-tusd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/token-tusd.png -------------------------------------------------------------------------------- /public/es-token-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/es-token-logo.png -------------------------------------------------------------------------------- /public/twitter-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/twitter-banner.png -------------------------------------------------------------------------------- /public/unknown-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/unknown-token.png -------------------------------------------------------------------------------- /public/eclipse-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/eclipse-favicon.png -------------------------------------------------------------------------------- /public/eclipse-testnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/eclipse-testnet.png -------------------------------------------------------------------------------- /public/gas-station-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/gas-station-info.png -------------------------------------------------------------------------------- /public/testnet-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/testnet-favicon.png -------------------------------------------------------------------------------- /public/airdrop-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/airdrop-background.png -------------------------------------------------------------------------------- /public/instant-swap-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/instant-swap-icon.png -------------------------------------------------------------------------------- /public/passive-grass-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/passive-grass-banner.png -------------------------------------------------------------------------------- /public/passive-grass-plus-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/passive-grass-plus-one.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/owlto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/owlto.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/relay.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /public/thirdparty-bridges/gaszip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/gaszip.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/orbiter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/orbiter.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/stride.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/stride.png -------------------------------------------------------------------------------- /lib/typeUtils.ts: -------------------------------------------------------------------------------- 1 | export function assertNever(x: never): never { 2 | throw new Error(`assertNever: expected value to be never, got ${x} ${typeof x}`) 3 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-nesting": {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /public/thirdparty-bridges/alldomains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/alldomains.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/hyperlane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/hyperlane.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/minibridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/minibridge.png -------------------------------------------------------------------------------- /public/thirdparty-bridges/retrobridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/thirdparty-bridges/retrobridge.png -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Black.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Bold.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Light.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Ultra.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Ultra.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Medium.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-Regular.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-BoldItalic.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-BlackItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-BlackItalic.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-LightItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-LightItalic.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-MediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-MediumItalic.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-UltraItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-UltraItalic.otf -------------------------------------------------------------------------------- /public/fonts/mikado/HvDTrial_Mikado-RegularItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eclipse-Laboratories-Inc/eclipse-bridge-ux/HEAD/public/fonts/mikado/HvDTrial_Mikado-RegularItalic.otf -------------------------------------------------------------------------------- /lib/networkUtils.ts: -------------------------------------------------------------------------------- 1 | export enum Options { 2 | Mainnet = 'Mainnet', 3 | Testnet = 'Testnet', 4 | } 5 | 6 | export type OptionsLower = Lowercase 7 | 8 | export function toOptionsLower(o: Options): OptionsLower { 9 | return o.toLowerCase() as OptionsLower 10 | } -------------------------------------------------------------------------------- /app/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useWalletClient"; 2 | export * from "./useWallets"; 3 | export * from "./useOnLinkNewWallet"; 4 | export * from "./useStableSwitchWallet"; 5 | export * from "./useOnSetPrimaryWallet"; 6 | export * from "./useLinkedWallets"; 7 | export * from "./useAdaptedWallet"; 8 | -------------------------------------------------------------------------------- /lib/isDynamicEclipseNetworkId.ts: -------------------------------------------------------------------------------- 1 | export enum DynamicEclipseNetworkIds { 2 | Mainnet = 200, 3 | Testnet = 201, 4 | } 5 | 6 | export function isDynamicEclipseNetworkId( 7 | value: number, 8 | ): value is DynamicEclipseNetworkIds { 9 | return value in DynamicEclipseNetworkIds; 10 | } 11 | -------------------------------------------------------------------------------- /public/chevron-right-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/instrumentation.ts: -------------------------------------------------------------------------------- 1 | export async function register() { 2 | if (process.env.NEXT_RUNTIME === "nodejs") { 3 | const { default: Sentry } = await import("@sentry/nextjs"); 4 | Sentry.init({ 5 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, 6 | tracesSampleRate: 1.0, 7 | environment: process.env.NODE_ENV, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # API keys 2 | NEXT_PUBLIC_ENVIRONMENT_ID=your-dynamic-env-id 3 | ETHERSCAN_API_KEY=your-etherscan-api-key 4 | 5 | # Website metadata 6 | WEBSITE_TITLE="Bridge to Eclipse" 7 | WEBSITE_DESCRIPTION="Official Eclipse Bridge." 8 | 9 | # App configuration 10 | NEXT_PUBLIC_ECLIPSE_RPC=https://mainnetbeta-rpc.eclipse.xyz 11 | NEXT_PUBLIC_CURRENT_CHAIN=mainnet -------------------------------------------------------------------------------- /lib/stringUtils.ts: -------------------------------------------------------------------------------- 1 | export function truncateWalletAddress(str: string) { 2 | if (str.length <= 8) { 3 | return str; 4 | } 5 | 6 | const firstPart = str.slice(0, 4); 7 | const lastPart = str.slice(-4); 8 | 9 | return `${firstPart}•••${lastPart}`; 10 | } 11 | 12 | export function toKebabCase(str: string) { 13 | return str.toLowerCase().replace(" ", "-"); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /app/hooks/useWallets.ts: -------------------------------------------------------------------------------- 1 | import { useUserWallets, Wallet } from "@dynamic-labs/sdk-react-core"; 2 | 3 | export const useWallets = () => { 4 | const userWallets: Wallet[] = useUserWallets() as Wallet[]; 5 | const solWallet = userWallets.find((w) => w.chain === "SOL"); 6 | const evmWallet = userWallets.find((w) => w.chain === "EVM"); 7 | 8 | return { userWallets, solWallet, evmWallet }; 9 | }; 10 | -------------------------------------------------------------------------------- /app/hooks/useWalletContext.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { WalletFilterContext } from "@/app/providers/WalletFilterProvider"; 3 | 4 | export const useWalletFilter = () => { 5 | const context = useContext(WalletFilterContext); 6 | if (!context) { 7 | throw new Error( 8 | "useWalletFilter must be used within a WalletFilterContext" 9 | ); 10 | } 11 | return context; 12 | }; 13 | -------------------------------------------------------------------------------- /app/components/icons/arrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Arrow = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default Arrow; 12 | -------------------------------------------------------------------------------- /app/mint-teth/constants/contracts.ts: -------------------------------------------------------------------------------- 1 | export const tellerAddress = "0x6Ae187EacF40ebd1e571a655dB92A1f47452E0Bf"; 2 | export const boringVaultAddress = "0x19e099B7aEd41FA52718D780dDA74678113C0b32"; 3 | export const accountantAddress = "0x8c1902A5996978F2628558DD93d309F7e3926dfD"; 4 | export const warpRouteContractAddress = "0xFbE0c54d696Fa6c93f35D4CA7D656561816C15F6"; 5 | export const atomicQueueContractAddress = "0x228c44bb4885c6633f4b6c83f14622f37d5112e5"; 6 | -------------------------------------------------------------------------------- /app/mint-tusd/constants/contracts.ts: -------------------------------------------------------------------------------- 1 | export const tellerAddress = "0x9Ff67b826b34feE9bf4135962754a808C4a14Fbc"; 2 | export const boringVaultAddress = "0x722a851B6798D65b80526562Fc3a36E19b1F883b"; 3 | export const accountantAddress = "0x66455247E6f56Cb60DfcAeA669037841a8414804"; 4 | export const warpRouteContractAddress = 5 | "0x762fCFcA1cF1958cf48cf6c8E3E8238c20513a02"; 6 | export const atomicQueueContractAddress = 7 | "0x228c44bb4885c6633f4b6c83f14622f37d5112e5"; // --- 8 | -------------------------------------------------------------------------------- /public/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/classnameUtils.ts: -------------------------------------------------------------------------------- 1 | import type { ClassValue } from 'clsx'; 2 | import { clsx } from 'clsx'; 3 | import { twMerge } from 'tailwind-merge'; 4 | 5 | /** 6 | * Returns a merged class name string by merging and processing multiple class names and Tailwind CSS styles. 7 | * 8 | * @param {...string[]} args - One or more class names and/or Tailwind CSS styles to be merged. 9 | * @returns {string} - The merged class name string. 10 | */ 11 | export function cn(...args: ClassValue[]): string { 12 | return twMerge(clsx(args)); 13 | } 14 | -------------------------------------------------------------------------------- /lib/walletClient.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient, custom } from 'viem'; 2 | import { mainnet, sepolia } from "viem/chains"; 3 | 4 | export const createWalletClientInstance = () => { 5 | if (typeof window !== 'undefined' && window.ethereum) { 6 | return createWalletClient({ 7 | chain: (process.env.NEXT_PUBLIC_CURRENT_CHAIN === "mainnet") ? mainnet : sepolia, 8 | transport: custom(window.ethereum!), 9 | }); 10 | } 11 | return null; 12 | }; 13 | 14 | export const walletClient = createWalletClientInstance(); 15 | -------------------------------------------------------------------------------- /app/global-error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as Sentry from "@sentry/nextjs"; 4 | import { useEffect } from "react"; 5 | 6 | export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { 7 | useEffect(() => { 8 | Sentry.captureException(error); 9 | }, [error]); 10 | 11 | return ( 12 |
13 |
14 |

Something went wrong!

15 | 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /public/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/mint-teth/abis/GasRouter.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "uint32", 7 | "name": "_destinationDomain", 8 | "type": "uint32" 9 | } 10 | ], 11 | "name": "quoteGasPayment", 12 | "outputs": [ 13 | { 14 | "internalType": "uint256", 15 | "name": "_gasPayment", 16 | "type": "uint256" 17 | } 18 | ], 19 | "stateMutability": "view", 20 | "type": "function" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /app/mint-tusd/abis/GasRouter.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "uint32", 7 | "name": "_destinationDomain", 8 | "type": "uint32" 9 | } 10 | ], 11 | "name": "quoteGasPayment", 12 | "outputs": [ 13 | { 14 | "internalType": "uint256", 15 | "name": "_gasPayment", 16 | "type": "uint256" 17 | } 18 | ], 19 | "stateMutability": "view", 20 | "type": "function" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /app/hooks/useAboutGasStationModal.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import { AboutGasStationComponent } from "@/app/components/GasStation/AboutGasStation"; 4 | 5 | export const useAboutGasStationModal = () => { 6 | const [isOpen, setIsOpen] = useState(false); 7 | 8 | const open = () => setIsOpen(true); 9 | const close = () => setIsOpen(false); 10 | 11 | return { 12 | open, 13 | close, 14 | isOpen, 15 | renderModal: ( 16 | 17 | ), 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /app/hooks/useStableSwitchWallet.ts: -------------------------------------------------------------------------------- 1 | import { useSwitchWallet } from "@dynamic-labs/sdk-react-core"; 2 | import { useEffect, useRef } from "react"; 3 | 4 | // This hook ensures that switchWallets is always up to date with the latest function from dynamic 5 | export const useStableSwitchWallet = () => { 6 | const switchWallet = useRef<(walletId: string) => Promise>(); 7 | const _switchWallet = useSwitchWallet(); 8 | 9 | useEffect(() => { 10 | switchWallet.current = _switchWallet; 11 | }, [_switchWallet]); 12 | 13 | return switchWallet; 14 | }; 15 | -------------------------------------------------------------------------------- /app/components/icons/chevron-right-small.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Chevron = ({ size }: { size?: string }) => { 4 | const isize = size ?? "24" 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default Chevron; 13 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/bridge.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BridgeIcon = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default BridgeIcon; 12 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/faucet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FaucetIcon = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default FaucetIcon; 12 | -------------------------------------------------------------------------------- /app/components/icons/activity.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Activity = ({ activityClassName }: { activityClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default Activity; 12 | -------------------------------------------------------------------------------- /public/token-rsweth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/components/constants/index.ts: -------------------------------------------------------------------------------- 1 | import abi from "./abi.json"; 2 | 3 | export const MIN_DEPOSIT_AMOUNT = 0.002; 4 | export const MIN_WITHDRAWAL_AMOUNT = 0.01; 5 | 6 | export const CONTRACT_ABI = abi; 7 | 8 | export const DEPOSIT_TX_GAS_LIMIT: number = 113200; // max actually looks like 88100; overestimate for now 9 | export const WITHDRAW_TX_FEE: number = 0.00000005 // ether, coming from Eclipse 10 | 11 | export const ECLIPSESCAN_BASE_URL = 'https://eclipsescan.xyz' 12 | 13 | export const ETHERSCAN_MAINNET_URL = 'https://etherscan.io' 14 | export const ETHERSCAN_TESTNET_URL = 'https://sepolia.etherscan.io' -------------------------------------------------------------------------------- /app/instrumentation-client.ts: -------------------------------------------------------------------------------- 1 | export async function register() { 2 | if (process.env.NEXT_RUNTIME === "browser") { 3 | const { default: Sentry } = await import("@sentry/nextjs"); 4 | const { Replay } = await import("@sentry/replay"); 5 | 6 | Sentry.init({ 7 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, 8 | tracesSampleRate: 1.0, 9 | replaysSessionSampleRate: 0.1, 10 | replaysOnErrorSampleRate: 1.0, 11 | integrations: [ 12 | new Replay({ 13 | maskAllText: true, 14 | blockAllMedia: true, 15 | }), 16 | ], 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mint-teth/lib/quoteGasPayment.ts: -------------------------------------------------------------------------------- 1 | import { Abi, Address, PublicClient } from "viem"; 2 | import GasRouter from "../abis/GasRouter.json"; 3 | 4 | export async function quoteGasPayment( 5 | { destinationDomain }: { destinationDomain: number }, 6 | { publicClient, contractAddress }: { publicClient: PublicClient; contractAddress: Address } 7 | ): Promise { 8 | const gasPayment = await publicClient.readContract({ 9 | abi: GasRouter.abi as Abi, 10 | address: contractAddress, 11 | functionName: "quoteGasPayment", 12 | args: [destinationDomain], 13 | }); 14 | 15 | return gasPayment as bigint; 16 | } 17 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/quoteGasPayment.ts: -------------------------------------------------------------------------------- 1 | import { Abi, Address, PublicClient } from "viem"; 2 | import GasRouter from "../abis/GasRouter.json"; 3 | 4 | export async function quoteGasPayment( 5 | { destinationDomain }: { destinationDomain: number }, 6 | { publicClient, contractAddress }: { publicClient: PublicClient; contractAddress: Address } 7 | ): Promise { 8 | const gasPayment = await publicClient.readContract({ 9 | abi: GasRouter.abi as Abi, 10 | address: contractAddress, 11 | functionName: "quoteGasPayment", 12 | args: [destinationDomain], 13 | }); 14 | 15 | return gasPayment as bigint; 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # we use yarn 9 | package-lock.json 10 | pnpm-lock.yaml 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | 41 | # Add .env 42 | .env 43 | -------------------------------------------------------------------------------- /app/components/icons/instant-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const InstantIcon = ({ className }: { className: string}) => { 4 | return ( 5 | 13 | 18 | 19 | ); 20 | } 21 | 22 | export default InstantIcon; 23 | -------------------------------------------------------------------------------- /app/sentry-example-page/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import * as Sentry from "@sentry/nextjs"; 5 | 6 | export default function SentryExamplePage() { 7 | useEffect(() => { 8 | // Test error tracking 9 | try { 10 | throw new Error("This is a test error for Sentry"); 11 | } catch (error) { 12 | Sentry.captureException(error); 13 | } 14 | }, []); 15 | 16 | return ( 17 |
18 |

Sentry Test Page

19 |

This page is used to test Sentry error tracking.

20 |

Check your Sentry dashboard to see the test error.

21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import { useState, type ReactNode } from "react"; 5 | import { NetworkProvider } from "@/app/contexts/NetworkContext"; 6 | import { SidebarProvider } from "@/app/contexts/SidebarContext"; 7 | 8 | 9 | export function Providers(props: { children: ReactNode }) { 10 | const [queryClient] = useState(() => new QueryClient()); 11 | 12 | return ( 13 | 14 | 15 | {props.children} 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /app/mint-teth/lib/getRateInQuote.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient, WalletClient } from "viem"; 2 | import AccountantWithRateProviders from "../abis/AccountantWithRateProviders.json"; 3 | import { accountantAddress } from "../constants/contracts"; 4 | 5 | export async function getRateInQuote( 6 | { quote }: { quote: `0x${string}` }, 7 | { publicClient }: { publicClient: PublicClient } 8 | ): Promise { 9 | const rate = await publicClient.readContract({ 10 | abi: AccountantWithRateProviders.abi as Abi, 11 | address: accountantAddress, 12 | functionName: "getRateInQuote", 13 | args: [quote], 14 | }); 15 | 16 | return rate as bigint; 17 | } 18 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/getRateInQuote.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient, WalletClient } from "viem"; 2 | import AccountantWithRateProviders from "../abis/AccountantWithRateProviders.json"; 3 | import { accountantAddress } from "../constants/contracts"; 4 | 5 | export async function getRateInQuote( 6 | { quote }: { quote: `0x${string}` }, 7 | { publicClient }: { publicClient: PublicClient } 8 | ): Promise { 9 | const rate = await publicClient.readContract({ 10 | abi: AccountantWithRateProviders.abi as Abi, 11 | address: accountantAddress, 12 | functionName: "getRateInQuote", 13 | args: [quote], 14 | }); 15 | 16 | return rate as bigint; 17 | } 18 | -------------------------------------------------------------------------------- /app/components/icons/thirdparty-bridges-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ThirdpartyBridgesIcon = ({ bridgesClassName }: { bridgesClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default ThirdpartyBridgesIcon; 12 | -------------------------------------------------------------------------------- /app/components/icons/disconnect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Disconnect = ({ disconnectClassName }: { disconnectClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default Disconnect; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/components/icons/arrow-up-right.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ArrowUpRight = ({ className }: { className: string}) => { 4 | return ( 5 | 16 | 22 | 23 | ); 24 | }; 25 | 26 | export default ArrowUpRight; 27 | -------------------------------------------------------------------------------- /app/mint-teth/lib/latestRoundData.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import Chainlink from "../abis/Chainlink.json"; 3 | 4 | export interface LatestRoundDataResult { 5 | roundId: bigint; 6 | answer: bigint; 7 | startedAt: bigint; 8 | updatedAt: bigint; 9 | answeredInRound: bigint; 10 | } 11 | 12 | export async function latestRoundData({ publicClient }: { publicClient: PublicClient }): Promise { 13 | const [roundId, answer, startedAt, updatedAt, answeredInRound] = (await publicClient.readContract({ 14 | abi: Chainlink.abi as Abi, 15 | address: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 16 | functionName: "latestRoundData", 17 | args: [], 18 | })) as bigint[]; 19 | 20 | return answer; 21 | } 22 | -------------------------------------------------------------------------------- /app/mint-teth/lib/warpcore.ts: -------------------------------------------------------------------------------- 1 | import { chainMetadata, eclipsemainnet, eclipsemainnetAddresses, warpRouteConfigs } from "@hyperlane-xyz/registry"; 2 | import { ChainMap, ChainMetadata, MultiProtocolProvider, WarpCore } from "@hyperlane-xyz/sdk"; 3 | import { Address } from "viem"; 4 | 5 | const chains: ChainMap = { 6 | eclipsemainnet: { 7 | ...eclipsemainnet, 8 | mailbox: eclipsemainnetAddresses.mailbox as Address, 9 | }, 10 | ethereum: { 11 | ...chainMetadata.ethereum, 12 | }, 13 | }; 14 | const multiProvider = new MultiProtocolProvider(chains); 15 | export const warpCore = WarpCore.FromConfig(multiProvider, { 16 | tokens: warpRouteConfigs["tETH/eclipsemainnet-ethereum"].tokens, 17 | }); 18 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/latestRoundData.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import Chainlink from "../abis/Chainlink.json"; 3 | 4 | export interface LatestRoundDataResult { 5 | roundId: bigint; 6 | answer: bigint; 7 | startedAt: bigint; 8 | updatedAt: bigint; 9 | answeredInRound: bigint; 10 | } 11 | 12 | export async function latestRoundData({ publicClient }: { publicClient: PublicClient }): Promise { 13 | const [roundId, answer, startedAt, updatedAt, answeredInRound] = (await publicClient.readContract({ 14 | abi: Chainlink.abi as Abi, 15 | address: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 16 | functionName: "latestRoundData", 17 | args: [], 18 | })) as bigint[]; 19 | 20 | return answer; 21 | } 22 | -------------------------------------------------------------------------------- /app/providers/GasProviders.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import { useState, type ReactNode } from "react"; 5 | import { SidebarProvider } from "@/app/contexts/SidebarContext"; 6 | import { WagmiProvider } from "@/app/providers/wagmiProvider"; 7 | 8 | export function GasProviders(props: { children: ReactNode }) { 9 | const [queryClient] = useState(() => new QueryClient()); 10 | 11 | return ( 12 | 13 | {() => { 14 | return ( 15 | 16 | {props.children} 17 | 18 | ); 19 | }} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/components/icons/warning-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const WarningIcon = ({ className }: { className: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default WarningIcon; 12 | -------------------------------------------------------------------------------- /public/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/hooks/useLinkedWallets.ts: -------------------------------------------------------------------------------- 1 | import { convertToLinkedWallet } from "@/lib/relay"; 2 | import { useUserWallets, Wallet } from "@dynamic-labs/sdk-react-core"; 3 | import { useMemo, useRef } from "react"; 4 | 5 | export const useLinkedWallets = () => { 6 | const userWallets = useUserWallets(); 7 | const wallets = useRef[]>(); 8 | 9 | const linkedWallets = useMemo(() => { 10 | const _wallets = userWallets.reduce( 11 | (linkedWallets, wallet) => { 12 | linkedWallets.push(convertToLinkedWallet(wallet)); 13 | return linkedWallets; 14 | }, 15 | [] as ReturnType[], 16 | ); 17 | wallets.current = userWallets; 18 | return _wallets; 19 | }, [userWallets]); 20 | 21 | return { linkedWallets, wallets }; 22 | }; 23 | -------------------------------------------------------------------------------- /app/components/deprecated/ExtendedDetails/styles.css: -------------------------------------------------------------------------------- 1 | .extended-details { 2 | max-width: 520px; 3 | width: 100%; 4 | border: 1px solid rgba(255, 255, 255, 0.1); 5 | background: rgba(255, 255, 255, 0.02); 6 | border-radius: 30px; 7 | } 8 | 9 | .white-text { 10 | color: white; 11 | font-size: 16px; 12 | font-weight: 500; 13 | } 14 | 15 | .green-text { 16 | color: rgba(161, 254, 160, 1); 17 | font-size: 16px; 18 | font-weight: 500; 19 | line-height: 20.8px; 20 | } 21 | 22 | .gray-text { 23 | font-size: 16px; 24 | line-height: 19.2px; 25 | color: rgba(255, 255, 255, 0.3); 26 | } 27 | 28 | .single-line { 29 | padding: 10px 20px 10px 20px; 30 | border-bottom: 1px solid rgba(255, 255, 255, 0.1); 31 | } 32 | 33 | .single-line:nth-child(3) { 34 | border-bottom: none; 35 | } 36 | -------------------------------------------------------------------------------- /public/gas.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/warpcore.ts: -------------------------------------------------------------------------------- 1 | import { 2 | chainMetadata, 3 | eclipsemainnet, 4 | eclipsemainnetAddresses, 5 | warpRouteConfigs, 6 | } from "@hyperlane-xyz/registry"; 7 | import { 8 | ChainMap, 9 | ChainMetadata, 10 | MultiProtocolProvider, 11 | WarpCore, 12 | } from "@hyperlane-xyz/sdk"; 13 | import { Address } from "viem"; 14 | 15 | const chains: ChainMap = { 16 | eclipsemainnet: { 17 | ...eclipsemainnet, 18 | mailbox: eclipsemainnetAddresses.mailbox as Address, 19 | }, 20 | ethereum: { 21 | ...chainMetadata.ethereum, 22 | }, 23 | }; 24 | const multiProvider = new MultiProtocolProvider(chains); 25 | export const warpCore = WarpCore.FromConfig(multiProvider, { 26 | tokens: warpRouteConfigs["tUSD/eclipsemainnet-ethereum"].tokens, 27 | }); 28 | -------------------------------------------------------------------------------- /app/hooks/useWalletClient.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useContext } from "react"; 2 | import { mainnet, sepolia } from "viem/chains"; 3 | import { createWalletClient, WalletClient, custom } from 'viem'; 4 | import { NetworkContext } from "@/app/contexts/NetworkContext"; 5 | import { Options } from "@/lib/networkUtils"; 6 | 7 | export const useWalletClient = (): WalletClient | null => { 8 | const context = useContext(NetworkContext); 9 | 10 | return useMemo(() => { 11 | if (typeof window === 'undefined' || !window.ethereum) return null; 12 | 13 | const chain = (context?.selectedOption === Options.Mainnet) ? mainnet : sepolia; 14 | 15 | return createWalletClient({ 16 | chain, 17 | transport: custom(window.ethereum), 18 | cacheTime: 0 19 | }); 20 | }, [context?.selectedOption]); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /lib/octaneUtils.ts: -------------------------------------------------------------------------------- 1 | const OCTANE_BASE_ADDRESS = "https://octane-server-alpha.vercel.app/api" 2 | 3 | export async function createOctaneSwapTransaction(user: string, tokenMint: string, amount: number) { 4 | const response = await fetch(`${OCTANE_BASE_ADDRESS}/buildWhirlpoolsSwap`, { 5 | method: 'POST', 6 | headers: { 'Content-Type': 'application/json' }, 7 | body: JSON.stringify({ 8 | user: user, 9 | sourceMint: tokenMint, 10 | amount: amount, 11 | slippingTolerance: 1 12 | }), 13 | }); 14 | 15 | return response.json() 16 | } 17 | 18 | export async function fetchOctaneConfig() { 19 | const response = await fetch(`${OCTANE_BASE_ADDRESS}`, { 20 | method: 'GET', 21 | headers: { 'Content-Type': 'application/json' } 22 | }); 23 | 24 | return response.json() 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { WalletClient } from 'viem'; 3 | 4 | export type DataArray = [(number | null), (number | null), (number | null)] // gas price, eth price, block number 5 | export const EthereumDataContext = createContext(null); 6 | 7 | let walletClient: any; 8 | export const WalletClientContext = createContext(walletClient); 9 | 10 | export type WalletState = { 11 | eclipseAddr: string; 12 | setEclipseAddr: React.Dispatch>; 13 | isValid: boolean | null; 14 | setIsValid: React.Dispatch>; 15 | } 16 | export const EclipseWalletContext = createContext({ 17 | eclipseAddr: '', 18 | setEclipseAddr: () => {}, 19 | isValid: false, 20 | setIsValid: () => {} 21 | }); 22 | -------------------------------------------------------------------------------- /app/components/icons/block.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Block = ({ blockClassName }: { blockClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default Block; 12 | -------------------------------------------------------------------------------- /app/providers/WalletFilterProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, ReactNode, createContext } from "react"; 2 | 3 | interface WalletFilterContextState { 4 | walletFilter?: "EVM" | "SOL" | "BTC" | "ECLIPSE"; 5 | setWalletFilter: ( 6 | value: "EVM" | "SOL" | "BTC" | "ECLIPSE" | undefined 7 | ) => void; 8 | } 9 | 10 | export const WalletFilterContext = createContext< 11 | WalletFilterContextState | undefined 12 | >(undefined); 13 | 14 | export const WalletFilterProvider: React.FC<{ children: ReactNode }> = ({ 15 | children, 16 | }) => { 17 | const [walletFilter, setWalletFilter] = useState< 18 | "EVM" | "SOL" | "BTC" | "ECLIPSE" | undefined 19 | >(undefined); 20 | 21 | return ( 22 | 23 | {children} 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /app/components/icons/gas.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Gas = ({ gasClassName }: { gasClassName: string }) => { 4 | return ( 5 | 13 | 21 | 22 | ); 23 | }; 24 | 25 | export default Gas; 26 | -------------------------------------------------------------------------------- /public/token-ezeth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/components/icons/connect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ConnectIcon = ({ connectClassName }: { connectClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default ConnectIcon; 13 | -------------------------------------------------------------------------------- /app/components/icons/eth.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Eth = ({ ethClassName }: { ethClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default Eth; 13 | -------------------------------------------------------------------------------- /app/components/ExtendedDetails/styles.css: -------------------------------------------------------------------------------- 1 | .tgreen { color: rgba(161, 254, 160, 1); } 2 | .gray-text { 3 | font-size: 16px; 4 | line-height: 19.2px; 5 | color: rgba(255, 255, 255, 0.3); 6 | } 7 | .green-text { 8 | color: rgba(161, 254, 160, 1); 9 | font-size: 16px; 10 | font-weight: 500; 11 | line-height: 20.8px; 12 | } 13 | 14 | .tx-sum { 15 | border: 1px solid rgba(255, 255, 255, 0.1); 16 | border-radius: 10px; 17 | 18 | background: rgba(13, 13, 13, 1); 19 | font-size: 14px; 20 | font-weight: 500; 21 | 22 | max-width: 480px; 23 | height: 88px; 24 | } 25 | 26 | .route-box { 27 | border-bottom: 1px solid rgba(255, 255, 255, 0.1); 28 | height: 44px; 29 | padding: 8px 16px; 30 | } 31 | 32 | .route-text { 33 | background: rgba(255, 255, 255, 0.05); 34 | color: rgba(255, 255, 255, 0.3); 35 | width: 62px; 36 | border-radius: 100px; 37 | padding: 4px 8px; 38 | } 39 | 40 | .amount-sum { 41 | padding: 8px 16px; 42 | } -------------------------------------------------------------------------------- /app/mint-teth/lib/getRate.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import AccountantWithRateProviders from "../abis/AccountantWithRateProviders.json"; 3 | import { accountantAddress } from "../constants/contracts"; 4 | 5 | export async function getRate( 6 | { tokenAddress }: { tokenAddress: `0x${string}` }, 7 | { publicClient }: { publicClient: PublicClient } 8 | ): Promise { 9 | const [isPeggedToBase, rateProviderAddress] = (await publicClient.readContract({ 10 | abi: AccountantWithRateProviders.abi as Abi, 11 | address: accountantAddress, 12 | functionName: "rateProviderData", 13 | args: [tokenAddress], 14 | })) as [boolean, `0x${string}`]; 15 | 16 | const rate = await publicClient.readContract({ 17 | abi: AccountantWithRateProviders.abi as Abi, 18 | address: rateProviderAddress as `0x${string}`, 19 | functionName: "getRate", 20 | args: [], 21 | }); 22 | 23 | return rate as bigint; 24 | } 25 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/getRate.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import AccountantWithRateProviders from "../abis/AccountantWithRateProviders.json"; 3 | import { accountantAddress } from "../constants/contracts"; 4 | 5 | export async function getRate( 6 | { tokenAddress }: { tokenAddress: `0x${string}` }, 7 | { publicClient }: { publicClient: PublicClient } 8 | ): Promise { 9 | const [isPeggedToBase, rateProviderAddress] = (await publicClient.readContract({ 10 | abi: AccountantWithRateProviders.abi as Abi, 11 | address: accountantAddress, 12 | functionName: "rateProviderData", 13 | args: [tokenAddress], 14 | })) as [boolean, `0x${string}`]; 15 | 16 | const rate = await publicClient.readContract({ 17 | abi: AccountantWithRateProviders.abi as Abi, 18 | address: rateProviderAddress as `0x${string}`, 19 | functionName: "getRate", 20 | args: [], 21 | }); 22 | 23 | return rate as bigint; 24 | } 25 | -------------------------------------------------------------------------------- /app/providers/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import { useState, type ReactNode } from "react"; 5 | import { SidebarProvider } from "@/app/contexts/SidebarContext"; 6 | import { RelayKitProvider } from "@/app/providers/RelayKitProvider"; 7 | import { RelayChain } from "@reservoir0x/relay-sdk"; 8 | import { WalletFilterProvider } from "@/app/providers/WalletFilterProvider"; 9 | 10 | export function Providers(props: { 11 | chains: RelayChain[]; 12 | children: ReactNode; 13 | }) { 14 | const [queryClient] = useState(() => new QueryClient()); 15 | 16 | return ( 17 | 18 | 19 | 20 | {props.children} 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/gas-station.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const GasStationIcon = ({ size, stroke, opacity }: { size?: string, stroke?: string, opacity?: string}) => { 4 | const isize = size ?? "16"; 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default GasStationIcon; 13 | -------------------------------------------------------------------------------- /app/mint-tusd/utils/calculateMinimumMint.ts: -------------------------------------------------------------------------------- 1 | const WAD = { 2 | bigint: BigInt(1e6), 3 | number: 1e6, 4 | }; 5 | 6 | /** 7 | * Calculates the minimum mint amount after accounting for slippage. 8 | * 9 | * @param depositAmount - The amount to be deposited, represented as a bigint. 10 | * @param rate - The rate at which the deposit amount is converted, represented as a bigint. 11 | * @param mintSlippage - The slippage percentage to account for, represented as a number. Default is 0.5%. 12 | * @returns The minimum mint amount after slippage, represented as a bigint. 13 | */ 14 | export function calculateMinimumMint( 15 | depositAmount: bigint, 16 | rate: bigint, 17 | mintSlippage: number = 0.005 // 0.5% 18 | ): bigint { 19 | const slippageAsBigInt = BigInt((mintSlippage * WAD.number).toString()); 20 | const minimumMint = (depositAmount * WAD.bigint) / rate; 21 | const slippageAmount = (minimumMint * slippageAsBigInt) / WAD.bigint; 22 | return minimumMint - slippageAmount; 23 | } 24 | -------------------------------------------------------------------------------- /app/mint-teth/utils/calculateMinimumMint.ts: -------------------------------------------------------------------------------- 1 | const WAD = { 2 | bigint: BigInt(1e18), 3 | number: 1e18, 4 | }; 5 | 6 | /** 7 | * Calculates the minimum mint amount after accounting for slippage. 8 | * 9 | * @param depositAmount - The amount to be deposited, represented as a bigint. 10 | * @param rate - The rate at which the deposit amount is converted, represented as a bigint. 11 | * @param mintSlippage - The slippage percentage to account for, represented as a number. Default is 0.5%. 12 | * @returns The minimum mint amount after slippage, represented as a bigint. 13 | */ 14 | export function calculateMinimumMint( 15 | depositAmount: bigint, 16 | rate: bigint, 17 | mintSlippage: number = 0.005 // 0.5% 18 | ): bigint { 19 | const slippageAsBigInt = BigInt((mintSlippage * WAD.number).toString()); 20 | const minimumMint = (depositAmount * WAD.bigint) / rate; 21 | const slippageAmount = (minimumMint * slippageAsBigInt) / WAD.bigint; 22 | return minimumMint - slippageAmount; 23 | } 24 | -------------------------------------------------------------------------------- /app/components/icons/circle-check.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CircleCheck = ({ circleClassName }: { circleClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default CircleCheck; 12 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/wallet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const WalletIcon = ({ width }: { width?: string}) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default WalletIcon; 13 | 14 | -------------------------------------------------------------------------------- /app/components/WarningComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { WarningIcon } from "./icons"; 3 | import classNames from "classnames"; 4 | 5 | export interface WarningComponentProps { 6 | message: string; 7 | className?: string; 8 | textClassName?: string; 9 | } 10 | 11 | export const WarningComponent: React.FC = ({ 12 | message, 13 | className, 14 | textClassName, 15 | }) => { 16 | return ( 17 |
25 | 26 | 32 | {message} 33 | 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /app/mint-teth/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | interface TooltipProps { 5 | text: string; 6 | } 7 | 8 | export function Tooltip({ text }: TooltipProps) { 9 | return ( 10 |
11 | 14 | {/* Tooltip */} 15 |
16 | {text} 17 | {/* Tooltip Arrow */} 18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/mint-tusd/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | interface TooltipProps { 5 | text: string; 6 | } 7 | 8 | export function Tooltip({ text }: TooltipProps) { 9 | return ( 10 |
11 | 14 | {/* Tooltip */} 15 |
16 | {text} 17 | {/* Tooltip Arrow */} 18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/components/icons/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = ({ loadingClassName, style }: { loadingClassName: string, style: any}) => { 4 | return ( 5 | 13 | 17 | 18 | 25 | 26 | 27 | ); 28 | } 29 | 30 | export default Loading; 31 | -------------------------------------------------------------------------------- /app/mint-teth/lib/balanceOf.ts: -------------------------------------------------------------------------------- 1 | import { erc20Abi, PublicClient } from "viem"; 2 | 3 | /** 4 | * Retrieves the balance of a specific address for a given ERC20 token. 5 | * @param balanceAddress The address for which the balance needs to be retrieved. 6 | * @param tokenAddress The address of the ERC20 token. 7 | * @param chainId The chain ID for the token (optional). 8 | * @returns The balance of the specified address as a BigInt. 9 | */ 10 | export async function balanceOf({ 11 | tokenAddress, 12 | userAddress, 13 | publicClient, 14 | }: { 15 | tokenAddress: `0x${string}`; 16 | userAddress: `0x${string}`; 17 | publicClient: PublicClient; 18 | }) { 19 | if (tokenAddress === "0x") { 20 | throw new Error(`Error calling balanceOf(): tokenAddress cannot be "0x".`); 21 | } 22 | 23 | const balanceOfAsBigInt = publicClient.readContract({ 24 | abi: erc20Abi, 25 | address: tokenAddress, 26 | functionName: "balanceOf", 27 | args: [userAddress], 28 | }); 29 | 30 | return balanceOfAsBigInt; 31 | } 32 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/balanceOf.ts: -------------------------------------------------------------------------------- 1 | import { erc20Abi, PublicClient } from "viem"; 2 | 3 | /** 4 | * Retrieves the balance of a specific address for a given ERC20 token. 5 | * @param balanceAddress The address for which the balance needs to be retrieved. 6 | * @param tokenAddress The address of the ERC20 token. 7 | * @param chainId The chain ID for the token (optional). 8 | * @returns The balance of the specified address as a BigInt. 9 | */ 10 | export async function balanceOf({ 11 | tokenAddress, 12 | userAddress, 13 | publicClient, 14 | }: { 15 | tokenAddress: `0x${string}`; 16 | userAddress: `0x${string}`; 17 | publicClient: PublicClient; 18 | }) { 19 | if (tokenAddress === "0x") { 20 | throw new Error(`Error calling balanceOf(): tokenAddress cannot be "0x".`); 21 | } 22 | 23 | const balanceOfAsBigInt = publicClient.readContract({ 24 | abi: erc20Abi, 25 | address: tokenAddress, 26 | functionName: "balanceOf", 27 | args: [userAddress], 28 | }); 29 | 30 | return balanceOfAsBigInt; 31 | } 32 | -------------------------------------------------------------------------------- /app/components/icons/square-arrow-top-right.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SquareArrowTopRight = ({ className }: { className: string}) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default SquareArrowTopRight; 12 | -------------------------------------------------------------------------------- /app/mint-teth/lib/userAtomicRequest.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import AtomicQueue from "../abis/AtomicQueue.json"; 3 | import { atomicQueueContractAddress } from "../constants/contracts"; 4 | 5 | export async function getUserAtomicRequest( 6 | { 7 | userAddress, 8 | offerAddress, 9 | wantAddress, 10 | }: { 11 | userAddress: `0x${string}`; 12 | offerAddress: `0x${string}`; 13 | wantAddress: `0x${string}`; 14 | }, 15 | { publicClient }: { publicClient: PublicClient } 16 | ): Promise<{ 17 | deadline: bigint; 18 | atomicPrice: bigint; 19 | offerAmount: bigint; 20 | inSolve: boolean; 21 | }> { 22 | const [deadline, atomicPrice, offerAmount, inSolve] = (await publicClient.readContract({ 23 | abi: AtomicQueue.abi as Abi, 24 | address: atomicQueueContractAddress, 25 | functionName: "userAtomicRequest", 26 | args: [userAddress, offerAddress, wantAddress], 27 | })) as [bigint, bigint, bigint, boolean]; 28 | 29 | return { deadline, atomicPrice, offerAmount, inSolve }; 30 | } 31 | -------------------------------------------------------------------------------- /app/mint-tusd/lib/userAtomicRequest.ts: -------------------------------------------------------------------------------- 1 | import { Abi, PublicClient } from "viem"; 2 | import AtomicQueue from "../abis/AtomicQueue.json"; 3 | import { atomicQueueContractAddress } from "../constants/contracts"; 4 | 5 | export async function getUserAtomicRequest( 6 | { 7 | userAddress, 8 | offerAddress, 9 | wantAddress, 10 | }: { 11 | userAddress: `0x${string}`; 12 | offerAddress: `0x${string}`; 13 | wantAddress: `0x${string}`; 14 | }, 15 | { publicClient }: { publicClient: PublicClient } 16 | ): Promise<{ 17 | deadline: bigint; 18 | atomicPrice: bigint; 19 | offerAmount: bigint; 20 | inSolve: boolean; 21 | }> { 22 | const [deadline, atomicPrice, offerAmount, inSolve] = (await publicClient.readContract({ 23 | abi: AtomicQueue.abi as Abi, 24 | address: atomicQueueContractAddress, 25 | functionName: "userAtomicRequest", 26 | args: [userAddress, offerAddress, wantAddress], 27 | })) as [bigint, bigint, bigint, boolean]; 28 | 29 | return { deadline, atomicPrice, offerAmount, inSolve }; 30 | } 31 | -------------------------------------------------------------------------------- /app/mint-tusd/constants/tokens.ts: -------------------------------------------------------------------------------- 1 | import { SelectOption } from "../components/EcSelect"; 2 | 3 | export const tokenAddresses: `0x${string}`[] = [ 4 | "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC 5 | ]; 6 | 7 | export const tokenOptions: SelectOption[] = [ 8 | { 9 | value: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 10 | label: "USDC", 11 | imageSrc: "/token-usdc.svg", 12 | }, 13 | ]; 14 | 15 | export const chainOptions: SelectOption[] = [ 16 | { value: "eclipse", label: "Eclipse", imageSrc: "/eclipse.png" }, 17 | { value: "ethereum", label: "Ethereum", imageSrc: "/eth.png" }, 18 | ]; 19 | 20 | // tETH token address on Eclipse 21 | export const tethSvmTokenAddress = 22 | "GU7NS9xCwgNPiAdJ69iusFrRfawjDDPjeMBovhV1d4kn"; 23 | export const tethEvmTokenAddress = "0x19e099B7aEd41FA52718D780dDA74678113C0b32"; 24 | 25 | // tUSD token address on Eclipse 26 | export const tusdSvmTokenAddress = 27 | "27Kkn8PWJbKJsRZrxbsYDdedpUQKnJ5vNfserCxNEJ3R"; 28 | export const tusdEvmTokenAddress = "0x722a851B6798D65b80526562Fc3a36E19b1F883b"; 29 | -------------------------------------------------------------------------------- /app/mint-teth/abis/WarpRouteContract.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "contract ERC20", 7 | "name": "depositAsset", 8 | "type": "address" 9 | }, 10 | { 11 | "internalType": "uint256", 12 | "name": "depositAmount", 13 | "type": "uint256" 14 | }, 15 | { 16 | "internalType": "uint256", 17 | "name": "minimumMint", 18 | "type": "uint256" 19 | }, 20 | { 21 | "internalType": "bytes32", 22 | "name": "_recipient", 23 | "type": "bytes32" 24 | } 25 | ], 26 | "name": "depositAndBridge", 27 | "outputs": [ 28 | { 29 | "internalType": "uint256", 30 | "name": "sharesMinted", 31 | "type": "uint256" 32 | }, 33 | { 34 | "internalType": "bytes32", 35 | "name": "messageId", 36 | "type": "bytes32" 37 | } 38 | ], 39 | "stateMutability": "nonpayable", 40 | "type": "function" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /app/hooks/useOnSetPrimaryWallet.ts: -------------------------------------------------------------------------------- 1 | import { useStableSwitchWallet } from "./useStableSwitchWallet"; 2 | import { Wallet } from "@dynamic-labs/sdk-react-core"; 3 | 4 | //In some cases there's a race condition between connecting the wallet and having it available to switch to so we need to poll for it 5 | export const useOnSetPrimaryWallet = (wallets?: Wallet[]) => { 6 | const switchWallet = useStableSwitchWallet(); 7 | 8 | const onSetPrimaryWallet = async (address: string) => { 9 | const maxAttempts = 20; 10 | let attemptCount = 0; 11 | const timer = setInterval(async () => { 12 | attemptCount++; 13 | const newPrimaryWallet = wallets?.find( 14 | (wallet) => wallet.address === address 15 | ); 16 | if (attemptCount >= maxAttempts) { 17 | clearInterval(timer); 18 | return; 19 | } 20 | if (!newPrimaryWallet || !switchWallet.current) { 21 | return; 22 | } 23 | try { 24 | await switchWallet.current(newPrimaryWallet?.id); 25 | clearInterval(timer); 26 | } catch (e) {} 27 | }, 200); 28 | }; 29 | 30 | return onSetPrimaryWallet; 31 | }; 32 | -------------------------------------------------------------------------------- /app/contexts/SidebarContext.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { createContext, useState, ReactNode, useContext, useEffect } from 'react'; 4 | 5 | type SidebarContextType = { 6 | isSidebar: boolean; 7 | setIsSidebar: React.Dispatch>; 8 | }; 9 | 10 | export const SidebarContext = createContext(undefined); 11 | 12 | export const SidebarProvider = ({ children }: { children: ReactNode }) => { 13 | const [isSidebar, setIsSidebar] = useState(true); 14 | useEffect(() => { 15 | if (isSidebar) { 16 | document.documentElement.style.setProperty("--sidebar-width", `106.5px`); 17 | } else { 18 | document.documentElement.style.setProperty("--sidebar-width", `32.5px`); 19 | } 20 | }, [isSidebar]) 21 | 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | }; 28 | 29 | export const useSidebar = () => { 30 | const context = useContext(SidebarContext); 31 | if (!context) { 32 | throw new Error('useSidebar must be used within a SidebarProvider'); 33 | } 34 | return context; 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /app/components/TransactionPool/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { WithdrawObject } from "@/lib/withdrawUtils" 3 | 4 | export interface Transaction { 5 | hash: string; 6 | status: 'pending' | 'confirmed'; 7 | eclipseTxHash: string | null; 8 | pdaData: any | undefined; 9 | pda: PublicKey | null 10 | } 11 | 12 | export const defaultTransaction: Transaction = { 13 | hash: "", 14 | status: 'pending', 15 | eclipseTxHash: null, 16 | pdaData: undefined, 17 | pda: null 18 | }; 19 | 20 | 21 | export interface WithdrawActivity { 22 | amount: string, 23 | pda: string, 24 | transaction: any, 25 | } 26 | 27 | export interface TransactionContextType { 28 | transactions: Map; 29 | withdrawTransactions: Map; 30 | addTransactionListener: (txHash: string, l1Status: string) => void; 31 | getTransaction: (txHash: string) => Transaction | undefined; 32 | pendingTransactions: Transaction[]; 33 | deposits: any[] | null 34 | withdrawals: WithdrawObject[] | null; 35 | addNewDeposit: (txData: any) => void; 36 | setWithdrawals: React.Dispatch>; 37 | } 38 | -------------------------------------------------------------------------------- /app/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { ProfileAvatar } from "./ProfileAvatar"; 5 | 6 | export const Header: React.FC<{ isExtended: boolean }> = ({ isExtended }) => { 7 | return ( 8 |
9 |
13 | {isExtended ? ( 14 | Eclipse Logo 22 | ) : ( 23 | Eclipse Logo 31 | )} 32 |
33 | 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /app/components/icons/circle-info.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CircleInfo = ({ className }: { className: string }) => { 4 | return ( 5 | 13 | 21 | 29 | 30 | ); 31 | }; 32 | 33 | export default CircleInfo; 34 | -------------------------------------------------------------------------------- /lib/priceUtils.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | const BN = require("bn.js") 3 | import Decimal from "decimal.js"; 4 | 5 | const WHIRLPOOL_PROGRAM_ID = new PublicKey('whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc'); 6 | const PYTH_PUBLIC_KEY = new PublicKey('HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ') 7 | 8 | function findCorrectPool(mintA: PublicKey, mintB: PublicKey): PublicKey { 9 | // usdc 10 | if ([mintA.toBase58(), mintB.toBase58()].includes("AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE")) { 11 | return new PublicKey("2FR5TF3iDCLzGbWAuejR7LKiUL1J8ERnC1z2WGhC9s6D"); 12 | } 13 | 14 | // sol 15 | return new PublicKey("CFYaUSe34VBEoeKdJBXm9ThwsWoLaQ5stgiA3eUWBwV4"); 16 | } 17 | 18 | 19 | export async function fetchTokenPrice() { 20 | const response = await fetch("https://pools-api-eclipse.mainnet.orca.so/prices"); 21 | 22 | if (!response.ok) { 23 | throw new Error("Failed to fetch prices."); 24 | } 25 | 26 | const data = await response.json(); 27 | console.log(data, "data") 28 | 29 | return [ 30 | data.data.GU7NS9xCwgNPiAdJ69iusFrRfawjDDPjeMBovhV1d4kn, //TETH 31 | data.data.BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL // SOL 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /app/components/Deposit/NetworkSwitcher.css: -------------------------------------------------------------------------------- 1 | .switcher-main { 2 | position: relative; 3 | width: 90%; 4 | margin-left: 14px; 5 | margin-top: 14px; 6 | transition: width 0.3s var(--ease-out-quad); 7 | } 8 | 9 | .net-switcher { 10 | border-radius: 12px; 11 | padding-right: 10px; 12 | padding-left: 8px; 13 | cursor: pointer; 14 | background: rgba(161, 254, 160, 0.05); 15 | 16 | height: 38px; 17 | width: 100%; 18 | gap: 8px; 19 | span { 20 | font-size: 16px; 21 | color: rgba(161, 254, 160, 1); 22 | font-weight: 500; 23 | } 24 | 25 | path { 26 | stroke: rgba(161, 254, 160, 1); 27 | } 28 | } 29 | 30 | .network-options { 31 | position: absolute; 32 | margin-top: 56px; 33 | z-index: 500; 34 | 35 | border-radius: 15px; 36 | border: 1px solid rgba(255, 255, 255, 0.1); 37 | background: rgba(0, 0, 0, 0.05); 38 | backdrop-filter: blur(54px); 39 | width: 100%; 40 | padding: 8px; 41 | 42 | div { 43 | color: rgba(255, 255, 255, 0.6); 44 | padding: 10px 9px; 45 | font-size: 16px; 46 | border-radius: 5px; 47 | cursor: pointer; 48 | font-weight: 500; 49 | } 50 | 51 | div:hover { 52 | background: rgba(255, 255, 255, 0.03); 53 | color: white; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ErrorInfo } from "react"; 2 | import * as Sentry from "@sentry/nextjs"; 3 | 4 | interface Props { 5 | children: React.ReactNode; 6 | } 7 | 8 | interface State { 9 | hasError: boolean; 10 | } 11 | 12 | export class ErrorBoundary extends React.Component { 13 | public state: State = { 14 | hasError: false, 15 | }; 16 | 17 | public static getDerivedStateFromError(_: Error): State { 18 | return { hasError: true }; 19 | } 20 | 21 | public componentDidCatch(error: Error, errorInfo: ErrorInfo) { 22 | Sentry.captureException(error, { 23 | contexts: { 24 | react: { 25 | componentStack: errorInfo.componentStack, 26 | }, 27 | }, 28 | }); 29 | } 30 | 31 | public render() { 32 | if (this.state.hasError) { 33 | return ( 34 |
35 |
36 |

Something went wrong

37 |

Please try refreshing the page

38 |
39 |
40 | ); 41 | } 42 | 43 | return this.props.children; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/ellipse.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Ellipse = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Ellipse; 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/components/ThirdpartyBridgeModal/constants.ts: -------------------------------------------------------------------------------- 1 | import { ThirdpartyBridge } from './ThirdpartyBridgeItem'; 2 | 3 | export const thirdpartyBridges: ThirdpartyBridge[] = [ 4 | { name: "Hyperlane", address: "usenexus.org", iconAddr: "/thirdparty-bridges/hyperlane.png" }, 5 | { name: "Relay", address: "relay.link/bridge/eclipse?toCurrency=11111111111111111111111111111111", iconAddr: "/thirdparty-bridges/relay.png" }, 6 | { name: "Owlto", address: "owlto.finance/?to=Eclipse", iconAddr: "/thirdparty-bridges/owlto.png" }, 7 | { name: "Orbiter", address: "orbiter.finance/?source=Ethereum&dest=Eclipse&token=ETH", iconAddr: "/thirdparty-bridges/orbiter.png" }, 8 | { name: "Gas.zip", address: "gas.zip", iconAddr: "/thirdparty-bridges/gaszip.png" }, 9 | { name: "Mini Bridge", address: "minibridge.chaineye.tools/?dst=eclipse&mode=swap", iconAddr: "/thirdparty-bridges/minibridge.png" }, 10 | { name: "Stride", address: "bridge.stride.zone", iconAddr: "/thirdparty-bridges/stride.png" }, 11 | { name: "Alldomains Bridge", address: "bridge.alldomains.id", iconAddr: "/thirdparty-bridges/alldomains.png" }, 12 | { name: "Retro Bridge", address: "app.retrobridge.io/?tokenFrom=ETH&destination=Eclipse", iconAddr: "/thirdparty-bridges/retrobridge.png" }, 13 | ] 14 | 15 | -------------------------------------------------------------------------------- /app/components/icons/transferArrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TransferArrow = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default TransferArrow; 13 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import ClientLayout from './client-layout'; 3 | 4 | export const metadata: Metadata = { 5 | title: process.env.WEBSITE_TITLE, 6 | description: process.env.WEBSITE_DESCRIPTION, 7 | icons: [ 8 | { 9 | rel: 'icon', 10 | type: 'image/png', 11 | sizes: '200x200', 12 | url: process.env.NEXT_PUBLIC_CURRENT_CHAIN === 'mainnet' 13 | ? '/eclipse-favicon.png' 14 | : '/testnet-favicon.png', 15 | } 16 | ], 17 | openGraph: { 18 | title: process.env.WEBSITE_TITLE, 19 | description: process.env.WEBSITE_DESCRIPTION, 20 | images: [ 21 | { 22 | url: "/twitter-banner.png", 23 | width: 1200 , 24 | height: 675, 25 | } 26 | ], 27 | type: 'website', 28 | locale: 'en_US' 29 | }, 30 | twitter: { 31 | card: "summary_large_image", 32 | title: process.env.WEBSITE_TITLE, 33 | description: process.env.WEBSITE_DESCRIPTION, 34 | images: [ 35 | { 36 | url: "/twitter-banner.png", 37 | width: 2400, 38 | height: 1350, 39 | } 40 | ] 41 | } 42 | }; 43 | 44 | export default function RootLayout({ 45 | children, 46 | }: { 47 | children: React.ReactNode; 48 | }) { 49 | return {children}; 50 | } 51 | -------------------------------------------------------------------------------- /app/components/ThirdpartyBridgeModal/ThirdpartyBridgeItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from "react"; 4 | import { SquareArrowTopRight } from "../icons" 5 | 6 | export interface ThirdpartyBridge { 7 | name: string, 8 | address: string, 9 | iconAddr: string 10 | } 11 | 12 | export const ThirdpartyBridgeItem: React.FC<{ thirdpartyBridge: ThirdpartyBridge}> = ({ 13 | thirdpartyBridge 14 | }) => { 15 | return ( 16 | 17 |
24 |
25 | 26 |
27 | {thirdpartyBridge.name} 28 | 29 | { thirdpartyBridge.address.split("/")[0] } 30 | 31 |
32 |
33 | 34 |
35 |
36 | ); 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mint-teth/utils/getSolanaBalance.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { parseUnits } from "viem"; 3 | 4 | const eclipseRpcUrl = process.env.NEXT_PUBLIC_ECLIPSE_RPC; 5 | 6 | export async function getSolanaBalance(userAddress: string, tokenMint: string): Promise { 7 | if (!eclipseRpcUrl) { 8 | throw new Error("Eclipse RPC URL is not defined"); 9 | } 10 | 11 | const connection = new Connection(eclipseRpcUrl, "confirmed"); 12 | 13 | // Create PublicKey objects for the wallet and token mint 14 | const wallet = new PublicKey(userAddress); 15 | const tokenMintAsPublicKey = new PublicKey(tokenMint); 16 | 17 | // Fetch all token accounts for the given wallet and token mint 18 | const tokenAccounts = await connection.getParsedTokenAccountsByOwner(wallet, { 19 | mint: tokenMintAsPublicKey, 20 | }); 21 | 22 | // Initialize balance 23 | let balance = 0; 24 | 25 | // Accumulate balance across all token accounts for this mint 26 | // (Solana can split token balances across multiple accounts under the same wallet) 27 | tokenAccounts.value.forEach((accountInfo) => { 28 | const tokenAmount = accountInfo.account.data.parsed.info.tokenAmount.uiAmount; 29 | balance += tokenAmount; // Add balance from each token account 30 | }); 31 | 32 | return parseUnits(balance.toString(), 18); 33 | } 34 | -------------------------------------------------------------------------------- /app/mint-tusd/utils/getSolanaBalance.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { parseUnits } from "viem"; 3 | 4 | const eclipseRpcUrl = process.env.NEXT_PUBLIC_ECLIPSE_RPC; 5 | 6 | export async function getSolanaBalance(userAddress: string, tokenMint: string): Promise { 7 | if (!eclipseRpcUrl) { 8 | throw new Error("Eclipse RPC URL is not defined"); 9 | } 10 | 11 | const connection = new Connection(eclipseRpcUrl, "confirmed"); 12 | 13 | // Create PublicKey objects for the wallet and token mint 14 | const wallet = new PublicKey(userAddress); 15 | const tokenMintAsPublicKey = new PublicKey(tokenMint); 16 | 17 | // Fetch all token accounts for the given wallet and token mint 18 | const tokenAccounts = await connection.getParsedTokenAccountsByOwner(wallet, { 19 | mint: tokenMintAsPublicKey, 20 | }); 21 | 22 | // Initialize balance 23 | let balance = 0; 24 | 25 | // Accumulate balance across all token accounts for this mint 26 | // (Solana can split token balances across multiple accounts under the same wallet) 27 | tokenAccounts.value.forEach((accountInfo) => { 28 | const tokenAmount = accountInfo.account.data.parsed.info.tokenAmount.uiAmount; 29 | balance += tokenAmount; // Add balance from each token account 30 | }); 31 | 32 | return parseUnits(balance.toString(), 6); 33 | } 34 | -------------------------------------------------------------------------------- /app/components/icons/copy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Copy = ({ copyClassName }: { copyClassName: string}) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default Copy; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /app/mint-teth/components/MintSummaryCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { formatUnits } from "viem"; 3 | import "./styles.css"; 4 | import { tokenOptions } from "../constants/tokens"; 5 | 6 | interface MintSummaryCardProps { 7 | depositAsset: string; 8 | exchangeRate: string; 9 | } 10 | 11 | export function MintSummaryCard({ depositAsset, exchangeRate }: MintSummaryCardProps) { 12 | // Derived values 13 | const depositAssetSymbol = tokenOptions.find((token) => token.value === depositAsset)?.label; 14 | const tEthValue = exchangeRate ? 1 / Number(formatUnits(BigInt(exchangeRate), 18)) : 0; 15 | const formattedExchangeRate = `1 ${depositAssetSymbol} = ${tEthValue.toFixed(3)} tETH`; 16 | 17 | return ( 18 |
19 |
20 |

{formattedExchangeRate}

21 |
22 |
23 | clock 24 |

~5 mins

25 |
26 |
27 |
28 |
29 |

Powered by

30 | nucleus 31 |

Nucleus

32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /app/mint-tusd/components/MintSummaryCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { formatUnits } from "viem"; 3 | import "./styles.css"; 4 | import { tokenOptions } from "../constants/tokens"; 5 | 6 | interface MintSummaryCardProps { 7 | depositAsset: string; 8 | exchangeRate: string; 9 | } 10 | 11 | export function MintSummaryCard({ 12 | depositAsset, 13 | exchangeRate, 14 | }: MintSummaryCardProps) { 15 | // Derived values 16 | const depositAssetSymbol = tokenOptions.find( 17 | (token) => token.value === depositAsset, 18 | )?.label; 19 | const tEthValue = exchangeRate 20 | ? 1 / Number(formatUnits(BigInt(exchangeRate), 6)) 21 | : 0; 22 | const formattedExchangeRate = `1 ${depositAssetSymbol} = ${tEthValue.toFixed(3)} tUSD`; 23 | 24 | return ( 25 |
26 |
27 |

28 | {formattedExchangeRate} 29 |

30 |
31 |
32 | clock 33 |

~10 mins

34 |
35 |
36 |
37 |
38 |

Powered by

39 | nucleus 40 |

Nucleus

41 |
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { withSentryConfig } = require("@sentry/nextjs"); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | webpack: (config) => { 6 | config.resolve.fallback = { 7 | net: false, 8 | tls: false, 9 | fs: false, 10 | path: require.resolve("path-browserify"), 11 | os: require.resolve("os-browserify/browser"), 12 | stream: require.resolve("stream-browserify"), 13 | child_process: false, // Disable 'child_process' 14 | }; 15 | return config; 16 | }, 17 | }; 18 | 19 | module.exports = withSentryConfig( 20 | nextConfig, 21 | { 22 | // For all available options, see: 23 | // https://github.com/getsentry/sentry-webpack-plugin#options. 24 | 25 | // Suppresses source map uploading logs during build 26 | silent: true, 27 | org: "your-org-name", 28 | project: "your-project-name", 29 | }, 30 | { 31 | // For all available options, see: 32 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ 33 | 34 | // Upload a larger set of source maps for prettier stack traces (increases build time) 35 | widenClientFileUpload: true, 36 | 37 | // Transpiles SDK to be compatible with IE11 (increases bundle size) 38 | transpileClientSDK: true, 39 | 40 | // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) 41 | tunnelRoute: "/monitoring", 42 | 43 | // Hides source maps from generated client bundles 44 | hideSourceMaps: true, 45 | 46 | // Automatically tree-shake Sentry logger statements to reduce bundle size 47 | disableLogger: true, 48 | } 49 | ); 50 | -------------------------------------------------------------------------------- /public/token-usdc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/mint-teth/constants/tokens.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "viem"; 2 | import { SelectOption } from "../components/EcSelect"; 3 | 4 | export const tokenAddresses: `0x${string}`[] = [ 5 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH 6 | "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", // weETH 7 | "0xbf5495efe5db9ce00f80364c8b423567e58d2110", // ezETH 8 | "0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6", // apxETH 9 | "0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0", // rswETH 10 | "0xD9A442856C234a39a81a089C06451EBAa4306a72", // pufETh 11 | ]; 12 | 13 | export const tokenOptions: SelectOption[] = [ 14 | { value: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", label: "WETH", imageSrc: "/token-weth.svg" }, 15 | { value: "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", label: "weETH", imageSrc: "/token-weeth.svg" }, 16 | /* 17 | { value: "0xbf5495efe5db9ce00f80364c8b423567e58d2110", label: "ezETH", imageSrc: "/token-ezeth.svg" }, 18 | { value: "0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6", label: "apxETH", imageSrc: "/token-apxeth.svg" }, 19 | { value: "0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0", label: "rswETH", imageSrc: "/token-rsweth.svg" }, 20 | { value: "0xD9A442856C234a39a81a089C06451EBAa4306a72", label: "pufETH", imageSrc: "/token-pufeth.svg" }, 21 | */ 22 | ]; 23 | 24 | export const chainOptions: SelectOption[] = [ 25 | { value: "eclipse", label: "Eclipse", imageSrc: "/eclipse.png" }, 26 | { value: "ethereum", label: "Ethereum", imageSrc: "/eth.png" }, 27 | ]; 28 | 29 | // tETH token address on Eclipse 30 | export const tethSvmTokenAddress = "GU7NS9xCwgNPiAdJ69iusFrRfawjDDPjeMBovhV1d4kn"; 31 | export const tethEvmTokenAddress = "0x19e099B7aEd41FA52718D780dDA74678113C0b32"; 32 | -------------------------------------------------------------------------------- /app/components/ThirdpartyBridgeModal/ThirdpartyBridgeModalContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useContext, ReactNode } from 'react'; 2 | 3 | interface ThirdpartyBridgeModalContextType { 4 | isThirdpartyBridgeModalOpen: boolean; 5 | setIsThirdpartyBridgeModalOpen: React.Dispatch>; 6 | toggleThirdpartyBridgeModal: () => void; 7 | } 8 | 9 | const ThirdpartyBridgeModalContext = createContext(undefined); 10 | 11 | interface ThirdpartyBridgeModalProviderProps { 12 | children: ReactNode; 13 | } 14 | 15 | export const ThirdpartyBridgeModalProvider: React.FC = ({ children }) => { 16 | const [isThirdpartyBridgeModalOpen, setIsThirdpartyBridgeModalOpen] = useState(false); 17 | 18 | 19 | const toggleThirdpartyBridgeModal = React.useCallback(() => { 20 | setIsThirdpartyBridgeModalOpen((prev) => { 21 | return !prev; 22 | }); 23 | }, [setIsThirdpartyBridgeModalOpen]); 24 | 25 | 26 | return ( 27 | 34 | {children} 35 | 36 | ); 37 | }; 38 | 39 | export const useThirdpartyBridgeModalContext = (): ThirdpartyBridgeModalContextType => { 40 | const context = useContext(ThirdpartyBridgeModalContext); 41 | if (!context) { 42 | throw new Error( 43 | 'useThirdpartyBridgeModalContext must be used within a ThirdpartyBridgeModalProvider' 44 | ); 45 | } 46 | return context; 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /app/mint-teth/types/index.ts: -------------------------------------------------------------------------------- 1 | export enum StepStatus { 2 | NOT_STARTED = "not-started", 3 | AWAITING_SIGNATURE = "awaiting-signature", 4 | LOADING = "loading", 5 | COMPLETED = "completed", 6 | CANCELLED = "cancelled", 7 | FAILED = "failed", 8 | EXPIRED = "expired", 9 | } 10 | 11 | export interface NucleusTransaction { 12 | id: number; 13 | user: string; 14 | offerToken: string; 15 | wantToken: string; 16 | amount: string; 17 | deadline: string; 18 | atomicPrice: string; 19 | createdTimestamp: string; 20 | endingTimestamp: string; 21 | createdLogIndex: number; 22 | createdTransactionIndex: number; 23 | createdBlockNumber: string; 24 | endingLogIndex: number; 25 | endingTransactionIndex: number; 26 | endingBlockNumber: string; 27 | status: string; 28 | queueAddress: string; 29 | chainId: number; 30 | offerAmountSpent: string; 31 | wantAmountRec: string; 32 | createdTransactionHash: string; 33 | endingTransactionHash: string; 34 | } 35 | 36 | export interface RawNucleusTransaction { 37 | id: number; 38 | user: string; 39 | offer_token: string; 40 | want_token: string; 41 | amount: string; 42 | deadline: string; 43 | atomic_price: string; 44 | created_timestamp: string; 45 | ending_timestamp: string; 46 | created_log_index: number; 47 | created_transaction_index: number; 48 | created_block_number: string; 49 | ending_log_index: number; 50 | ending_transaction_index: number; 51 | ending_block_number: string; 52 | status: string; 53 | queue_address: string; 54 | chain_id: number; 55 | offer_amount_spent: string; 56 | want_amount_rec: string; 57 | created_transaction_hash: string; 58 | ending_transaction_hash: string; 59 | } 60 | -------------------------------------------------------------------------------- /app/mint-tusd/types/index.ts: -------------------------------------------------------------------------------- 1 | export enum StepStatus { 2 | NOT_STARTED = "not-started", 3 | AWAITING_SIGNATURE = "awaiting-signature", 4 | LOADING = "loading", 5 | COMPLETED = "completed", 6 | CANCELLED = "cancelled", 7 | FAILED = "failed", 8 | EXPIRED = "expired", 9 | } 10 | 11 | export interface NucleusTransaction { 12 | id: number; 13 | user: string; 14 | offerToken: string; 15 | wantToken: string; 16 | amount: string; 17 | deadline: string; 18 | atomicPrice: string; 19 | createdTimestamp: string; 20 | endingTimestamp: string; 21 | createdLogIndex: number; 22 | createdTransactionIndex: number; 23 | createdBlockNumber: string; 24 | endingLogIndex: number; 25 | endingTransactionIndex: number; 26 | endingBlockNumber: string; 27 | status: string; 28 | queueAddress: string; 29 | chainId: number; 30 | offerAmountSpent: string; 31 | wantAmountRec: string; 32 | createdTransactionHash: string; 33 | endingTransactionHash: string; 34 | } 35 | 36 | export interface RawNucleusTransaction { 37 | id: number; 38 | user: string; 39 | offer_token: string; 40 | want_token: string; 41 | amount: string; 42 | deadline: string; 43 | atomic_price: string; 44 | created_timestamp: string; 45 | ending_timestamp: string; 46 | created_log_index: number; 47 | created_transaction_index: number; 48 | created_block_number: string; 49 | ending_log_index: number; 50 | ending_transaction_index: number; 51 | ending_block_number: string; 52 | status: string; 53 | queue_address: string; 54 | chain_id: number; 55 | offer_amount_spent: string; 56 | want_amount_rec: string; 57 | created_transaction_hash: string; 58 | ending_transaction_hash: string; 59 | } 60 | -------------------------------------------------------------------------------- /lib/solanaUtils.ts: -------------------------------------------------------------------------------- 1 | const solanaWeb3 = require("@solana/web3.js"); 2 | import { PublicKey, PublicKeyInitData, Connection } from "@solana/web3.js"; 3 | import { 4 | getAssociatedTokenAddress, 5 | TOKEN_2022_PROGRAM_ID, 6 | } from "@solana/spl-token"; 7 | import { toHex } from "viem"; 8 | 9 | export async function getWalletBalance(publicKey: String, eclipseRpc: string) { 10 | // Connect to the Solana mainnet 11 | const connection = new solanaWeb3.Connection(eclipseRpc, "confirmed"); 12 | 13 | // Fetch the balance 14 | const balance = await connection.getBalance( 15 | new solanaWeb3.PublicKey(publicKey), 16 | ); 17 | 18 | // Convert balance from lamports to SOL (1 SOL = 10^9 lamports) 19 | return balance / solanaWeb3.LAMPORTS_PER_SOL; 20 | } 21 | 22 | export async function getTokenBalance(tokenMint: string, wallet: string) { 23 | const eclipseRpcUrl = process.env.NEXT_PUBLIC_ECLIPSE_RPC; 24 | const connection = new Connection(eclipseRpcUrl ?? "", "finalized"); 25 | 26 | const mintAddress = new PublicKey(tokenMint); 27 | const walletAddress = new PublicKey(wallet); 28 | 29 | console.log(TOKEN_2022_PROGRAM_ID); 30 | const associatedTokenAddress = await getAssociatedTokenAddress( 31 | mintAddress, 32 | walletAddress, 33 | false, 34 | TOKEN_2022_PROGRAM_ID, 35 | ); 36 | try { 37 | const tokenAccount = await connection.getTokenAccountBalance( 38 | associatedTokenAddress, 39 | ); 40 | const tokenBalance = tokenAccount.value; 41 | 42 | return parseInt(tokenBalance.amount); 43 | } catch { 44 | return 0; 45 | } 46 | } 47 | 48 | export const solanaToBytes32 = (solanaAddress: PublicKeyInitData) => { 49 | try { 50 | const publicKey = new PublicKey(solanaAddress); 51 | return toHex(publicKey.toBytes().slice(0, 32)); 52 | } catch (error) { 53 | console.error("Invalid Solana address", error); 54 | throw new Error("Invalid Solana address"); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /app/api/get-transactions/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | const API_KEY = process.env.ETHERSCAN_API_KEY || ''; 4 | const chains: { [key: string]: [string, string, string] } = { 5 | mainnet: ['https://api.etherscan.io/api' , '0x2B08D7cF7EafF0f5f6623d9fB09b080726D4be11', "21126289"], 6 | testnet: ['https://api-sepolia.etherscan.io/api' , '0xe49aaa25a10fd6e15dd7ddcb50904ca1e91f6e01', "7025169"], 7 | }; 8 | 9 | export async function GET(request: Request) { 10 | const { searchParams } = new URL(request.url); 11 | const address = searchParams.get('address'); 12 | const chain = searchParams.get('chain'); 13 | console.log(chain, "chain") 14 | if (!chain || !chains[chain]) { 15 | return NextResponse.json({ message: 'Invalid chain' }, { status: 400 }); 16 | } 17 | 18 | if (!address || typeof address !== 'string') { 19 | return NextResponse.json({ message: 'Address is required' }, { status: 400 }); 20 | } 21 | const [etherscanApi, bridgeContract, startBlock] = chains[chain]; 22 | 23 | try { 24 | const apiUrl = `${etherscanApi}?module=account&action=txlist&address=${address}&startblock=${startBlock}&endblock=99999999&page=1&offset=1000&sort=asc&apikey=${API_KEY}`; 25 | console.log(apiUrl) 26 | const response = await fetch(apiUrl); 27 | const data = await response.json(); 28 | 29 | if (data.status !== '1') { 30 | throw new Error(data.message || 'Failed to fetch data from Etherscan'); 31 | } 32 | 33 | const deposits = data.result 34 | .filter( 35 | (tx: any) => 36 | tx.to.toLowerCase() === bridgeContract.toLowerCase() && 37 | tx.methodId === '0x1de26e16' 38 | ); 39 | 40 | // TODO: remove this 41 | return NextResponse.json(deposits.slice(0, 5)); 42 | } catch (error) { 43 | console.error('Error fetching last deposits:', error); 44 | return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/components/icons/index.js: -------------------------------------------------------------------------------- 1 | export { default as Activity } from "./activity"; 2 | export { default as Block } from "./block"; 3 | export { default as Chevron } from "./chevron-right-small"; 4 | export { default as CircleCheck } from "./circle-check"; 5 | export { default as ConnectIcon } from "./connect"; 6 | export { default as Copy } from "./copy"; 7 | export { default as Cross } from "./cross"; 8 | export { default as Disconnect } from "./disconnect"; 9 | export { default as Eth } from "./eth"; 10 | export { default as Gas } from "./gas"; 11 | export { default as Loading } from "./loading"; 12 | export { default as TransferArrow } from "./transferArrow"; 13 | export { default as Arrow } from "./arrow"; 14 | export { default as TransactionIcon } from "./transaction-icon"; 15 | export { default as ActivityBoxIcon } from "./activityBox"; 16 | export { default as BridgeIcon } from "./sidebar-icons/bridge"; 17 | export { default as WalletIcon } from "./sidebar-icons/wallet"; 18 | export { default as TethIcon } from "./sidebar-icons/teth"; 19 | export { default as TusdIcon } from "./sidebar-icons/tusd"; 20 | export { default as ScanIcon } from "./sidebar-icons/scan"; 21 | export { default as FaucetIcon } from "./sidebar-icons/faucet"; 22 | export { default as EcosystemIcon } from "./sidebar-icons/ecosystem"; 23 | export { default as GasStationIcon } from "./sidebar-icons/gas-station"; 24 | export { default as Ellipse } from "./sidebar-icons/ellipse"; 25 | export { default as ThirdpartyBridgesIcon } from "./thirdparty-bridges-icon"; 26 | export { default as SquareArrowTopRight } from "./square-arrow-top-right"; 27 | export { default as WarningIcon } from "./warning-icon"; 28 | export { default as InstantIcon } from "./instant-icon"; 29 | export { default as ArrowUpRight } from "./arrow-up-right"; 30 | export { default as CircleInfo } from "./circle-info"; 31 | export { default as GrassIcon } from "./grass"; 32 | export { default as PassiveGrassBanner } from "./passive-grass-banner"; 33 | -------------------------------------------------------------------------------- /app/hooks/useAdaptedWallet.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Wallet } from "@dynamic-labs/sdk-react-core"; 3 | import { WalletConnectorCore } from "@dynamic-labs/wallet-connector-core"; 4 | import { AdaptedWallet, adaptViemWallet } from "@reservoir0x/relay-sdk"; 5 | import { isSolanaWallet } from "@dynamic-labs/solana"; 6 | import { adaptSolanaWallet } from "@reservoir0x/relay-svm-wallet-adapter"; 7 | import { isEthereumWallet } from "@dynamic-labs/ethereum"; 8 | import { isEclipseWallet } from "@dynamic-labs/eclipse"; 9 | 10 | export const useAdaptedWallet = ( 11 | wallet: Wallet | null 12 | ) => { 13 | const [adaptedWallet, setAdaptedWallet] = useState< 14 | AdaptedWallet | undefined 15 | >(); 16 | 17 | useEffect(() => { 18 | const adaptWallet = async () => { 19 | try { 20 | if (wallet !== null) { 21 | let adaptedWallet: AdaptedWallet | undefined; 22 | if (isEthereumWallet(wallet)) { 23 | const walletClient = await wallet.getWalletClient(); 24 | adaptedWallet = adaptViemWallet(walletClient); 25 | } else if (isSolanaWallet(wallet) || isEclipseWallet(wallet)) { 26 | const connection = await (wallet as any).getConnection(); 27 | const signer = await (wallet as any).getSigner(); 28 | const _chainId = isEclipseWallet(wallet) ? 9286185 : 792703809; 29 | 30 | adaptedWallet = adaptSolanaWallet( 31 | wallet.address, 32 | _chainId, 33 | connection, 34 | signer.signAndSendTransaction 35 | ); 36 | } 37 | setAdaptedWallet(adaptedWallet); 38 | } else { 39 | setAdaptedWallet(undefined); 40 | } 41 | } catch (e) { 42 | setAdaptedWallet(undefined); 43 | } 44 | }; 45 | adaptWallet(); 46 | }, [wallet, wallet?.address]); 47 | 48 | return adaptedWallet; 49 | }; 50 | -------------------------------------------------------------------------------- /app/components/Sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | width: 215px; 3 | height: 100%; 4 | left: 0; 5 | border-right: 1px solid rgba(255, 255, 255, 0.1); 6 | transition: width 0.3s var(--ease-out-quad); 7 | } 8 | 9 | .side-icon-box { 10 | border: 1px solid rgba(255, 255, 255, 0.1); 11 | padding: 10px; 12 | background: rgba(255, 255, 255, 0.05); 13 | transition: background 0.1s var(--ease-out-quad); 14 | 15 | border-radius: 8px; 16 | } 17 | 18 | .sidebar-tabs { 19 | color: rgba(255, 255, 255, 0.3); 20 | gap: 8px; 21 | span { transition: color 0.1s var(--ease-out-quad); white-space: nowrap; } 22 | path { 23 | stroke: rgba(255, 255, 255, 0.3); 24 | stroke-opacity: 1; 25 | transition: stroke 0.1s var(--ease-out-quad); 26 | } 27 | } 28 | 29 | .side-item { 30 | border-radius: 12px; 31 | width: 187px; 32 | height: 46px; 33 | padding: 0px; 34 | transition: padding 0.3s var(--ease-out-quad); 35 | } 36 | 37 | .side-item:hover { 38 | cursor: pointer; 39 | color: rgba(255, 255, 255, 1); 40 | background: rgba(255, 255, 255, 0.05); 41 | 42 | path { 43 | stroke: rgba(255, 255, 255, 1); 44 | stroke-opacity: 1; 45 | } 46 | 47 | path.make-filled { 48 | fill: white; 49 | fill-opacity: 1; 50 | } 51 | .side-icon-box { 52 | background: rgba(255, 255, 255, 0.1); 53 | } 54 | } 55 | 56 | .sidebar-hover { 57 | background: rgba(255, 255, 255, 0.08); 58 | backdrop-filter: blur(4px); 59 | height: 24px; 60 | width: auto; 61 | margin-left: -12px; 62 | 63 | z-index: 500; 64 | padding: 4px 10px; 65 | font-size: 12px; 66 | color: white; 67 | font-weight: 500; 68 | border-radius: 5px; 69 | } 70 | 71 | .left-arr { 72 | font-size: 0px; line-height: 0%; width: 0px; 73 | border-top: 10px solid #f6f6f6; 74 | border-right: 20px solid #77c; 75 | border-bottom: 10px solid #f6f6f6; 76 | } 77 | 78 | .close-sidebar-icon path { 79 | transition: stroke-opacity 0.1s var(--ease-out-quad); 80 | } 81 | 82 | .close-sidebar-icon:hover { 83 | path { 84 | stroke-opacity: 0.8; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/components/GasStation/BridgeRedirectionComponent.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import { Token } from "./TokenManager"; 3 | 4 | type TokenBridge = { 5 | text: string; 6 | bridge: string; 7 | }; 8 | 9 | const tokenBridges: Record = { 10 | SOL: { 11 | text: "Bridge your SOL from Solana to Eclipse.", 12 | bridge: "https://usenexus.org", 13 | }, 14 | USDC: { 15 | text: "Bridge your USDC from any chain to Eclipse.", 16 | bridge: "https://usenexus.org", 17 | }, 18 | tETH: { 19 | text: "Mint tETH by depositing LRTs on Ethereum.", 20 | bridge: "/mint-teth", 21 | }, 22 | WIF: { text: "", bridge: "" }, 23 | }; 24 | 25 | const BridgeNowButton: React.FC<{ token: Token["symbol"] }> = ({ token }) => { 26 | const bridgeAddress = tokenBridges[token].bridge; 27 | return ( 28 | 39 | Bridge Now 40 | 41 | ); 42 | }; 43 | 44 | export const BridgeRedirectionComponent: React.FC<{ 45 | token: Token["symbol"]; 46 | }> = ({ token }) => { 47 | return ( 48 |
54 |
55 | 56 | You don't have any {token} on Eclipse right now. 57 | 58 | {tokenBridges[token].text} 59 |
60 | 61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /app/hooks/useOnLinkNewWallet.ts: -------------------------------------------------------------------------------- 1 | import { useWalletFilter } from "@/app/hooks/useWalletContext"; 2 | import { convertToLinkedWallet } from "@/lib/relay"; 3 | import { 4 | useDynamicEvents, 5 | useDynamicModals, 6 | } from "@dynamic-labs/sdk-react-core"; 7 | import { LinkedWallet } from "@reservoir0x/relay-kit-ui"; 8 | import { RelayChain } from "@reservoir0x/relay-sdk"; 9 | import { useState } from "react"; 10 | 11 | export const useOnLinkNewWallet = () => { 12 | const { setShowLinkNewWalletModal } = useDynamicModals(); 13 | const { setWalletFilter } = useWalletFilter(); 14 | const [linkWalletPromise, setLinkWalletPromise] = useState< 15 | | { 16 | resolve: (value: LinkedWallet) => void; 17 | reject: () => void; 18 | params: { chain?: RelayChain; direction: "to" | "from" }; 19 | } 20 | | undefined 21 | >(); 22 | 23 | useDynamicEvents("walletAdded", (newWallet) => { 24 | if (linkWalletPromise) { 25 | linkWalletPromise.resolve(convertToLinkedWallet(newWallet)); 26 | setLinkWalletPromise(undefined); 27 | } 28 | }); 29 | 30 | const onLinkNewWallet = ({ 31 | chain, 32 | direction, 33 | }: { 34 | chain?: RelayChain; 35 | direction: "to" | "from"; 36 | }) => { 37 | if (linkWalletPromise) { 38 | linkWalletPromise.reject(); 39 | setLinkWalletPromise(undefined); 40 | } 41 | if (chain?.vmType === "evm") { 42 | setWalletFilter("EVM"); 43 | } else if (chain?.id === 792703809) { 44 | setWalletFilter("SOL"); 45 | } else if (chain?.id === 8253038) { 46 | setWalletFilter("BTC"); 47 | } else if (chain?.id === 9286185) { 48 | setWalletFilter("ECLIPSE"); 49 | } else { 50 | setWalletFilter(undefined); 51 | } 52 | const promise = new Promise((resolve, reject) => { 53 | setLinkWalletPromise({ 54 | resolve, 55 | reject, 56 | params: { 57 | chain, 58 | direction, 59 | }, 60 | }); 61 | }); 62 | setShowLinkNewWalletModal(true); 63 | return promise; 64 | }; 65 | 66 | return onLinkNewWallet; 67 | }; 68 | -------------------------------------------------------------------------------- /app/components/icons/cross.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Cross = ({ crossClassName }: { crossClassName: string }) => { 4 | if (crossClassName === "wallets-cross") 5 | return ( 6 | 14 | 21 | 22 | ); 23 | 24 | if (crossClassName === "tap-cross") 25 | return ( 26 | 34 | 41 | 42 | ); 43 | 44 | return ( 45 | 53 | 60 | 61 | ); 62 | }; 63 | 64 | export default Cross; 65 | -------------------------------------------------------------------------------- /app/client-layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import "./globals.css"; 3 | import "@reservoir0x/relay-kit-ui/styles.css"; 4 | import { Providers } from "@/app/providers/providers"; 5 | import { usePathname } from "next/navigation"; 6 | import { IBM_Plex_Sans } from "next/font/google"; 7 | import { Suspense } from "react"; 8 | import { WagmiProvider } from "@/app/providers/wagmiProvider"; 9 | import { DynamicProvider } from "@/app/providers/DynamicProvider"; 10 | import { WalletFilterProvider } from "@/app/providers/WalletFilterProvider"; 11 | import { GasProviders } from "@/app/providers/GasProviders"; 12 | import { ErrorBoundary } from "@/app/components/ErrorBoundary"; 13 | 14 | const ibmPlexSans = IBM_Plex_Sans({ 15 | subsets: ["latin"], 16 | weight: ["400", "500", "600", "700"], 17 | }); 18 | 19 | export default function ClientLayout({ children }: { children: React.ReactNode }) { 20 | const pathname = usePathname(); 21 | const passGlobalLayout = pathname === "/gas-station"; 22 | if (passGlobalLayout) { 23 | return ( 24 | 25 | {() => { 26 | return ( 27 | 28 | 29 | {children} 30 | 31 | 32 | ); 33 | }} 34 | 35 | ); 36 | } 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {({ chains }) => { 47 | return ( 48 | 49 | 50 | 51 | {children} 52 | 53 | 54 | 55 | ); 56 | }} 57 | 58 | 59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /app/components/Deposit/RelaySwapWidget.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { useState } from "react"; 4 | 5 | import "./styles.css"; 6 | import { SwapWidget, Token } from "@reservoir0x/relay-kit-ui"; 7 | import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; 8 | import { useLinkedWallets } from "@/app/hooks/useLinkedWallets"; 9 | import { useOnLinkNewWallet } from "@/app/hooks/useOnLinkNewWallet"; 10 | import { useOnSetPrimaryWallet } from "@/app/hooks/useOnSetPrimaryWallet"; 11 | import { useAdaptedWallet } from "@/app/hooks/useAdaptedWallet"; 12 | 13 | export interface RelaySwapWidgetContentProps {} 14 | 15 | export const RelaySwapWidget: React.FC = ({}) => { 16 | const { setShowAuthFlow, primaryWallet } = useDynamicContext(); 17 | const { linkedWallets, wallets } = useLinkedWallets(); 18 | const onLinkNewWallet = useOnLinkNewWallet(); 19 | const onSetPrimaryWallet = useOnSetPrimaryWallet(wallets.current); 20 | const wallet = useAdaptedWallet(primaryWallet); 21 | const [fromToken, setFromToken] = useState({ 22 | address: "0x0000000000000000000000000000000000000000", 23 | chainId: 1, 24 | symbol: "ETH", 25 | name: "ETH", 26 | decimals: 18, 27 | logoURI: "https://assets.relay.link/icons/currencies/eth.png", 28 | }); 29 | const [toToken, setToToken] = useState({ 30 | address: "11111111111111111111111111111111", 31 | chainId: 9286185, 32 | symbol: "ETH", 33 | name: "ETH", 34 | decimals: 9, 35 | logoURI: "https://assets.relay.link/icons/currencies/eth.png", 36 | }); 37 | 38 | return ( 39 | setShowAuthFlow(true)} 41 | lockChainId={9286185} 42 | fromToken={fromToken} 43 | setFromToken={setFromToken} 44 | toToken={toToken} 45 | setToToken={setToToken} 46 | wallet={wallet} 47 | supportedWalletVMs={["evm", "svm", "bvm"]} 48 | multiWalletSupportEnabled={true} 49 | linkedWallets={linkedWallets} 50 | onLinkNewWallet={(params) => { 51 | return onLinkNewWallet(params); 52 | }} 53 | onSetPrimaryWallet={(address) => { 54 | onSetPrimaryWallet(address); 55 | }} 56 | /> 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /app/components/Deposit/AirdropPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Link from "next/link"; 3 | import { Cross, ArrowUpRight } from "../icons"; 4 | 5 | const AirdropPopup: React.FC = () => { 6 | const [popupVisible, setPopupVisible] = useState(false); 7 | 8 | useEffect(() => { 9 | const popupClosed = localStorage.getItem("airdropPopup"); 10 | if (popupClosed === null) { 11 | setPopupVisible(true); 12 | } 13 | }, []); 14 | 15 | const closePopup = () => { 16 | setPopupVisible(false); 17 | localStorage.setItem("airdropPopup", "true"); 18 | }; 19 | 20 | return ( 21 | <> 22 | {popupVisible && ( 23 | <> 24 |
25 |
26 | 32 |
33 | 34 | $ES Airdrop is Live! 35 | 36 | 37 |
38 | 44 | Claim Now 45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 | 57 | )} 58 | 59 | ); 60 | }; 61 | 62 | export default AirdropPopup; 63 | -------------------------------------------------------------------------------- /public/token-weeth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/mint-teth/utils/sanitizeInput.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitizes the deposit input by enforcing specific formatting rules: 3 | * 4 | * - If the user presses the decimal point with an empty input, the value is set to "0.". 5 | * - If the current deposit amount is "0" and the user enters a number between 1-9, 6 | * the leading zero is removed. 7 | * - The input can be an empty string or a valid number format (digits and an optional decimal point). 8 | * - If the input starts with multiple leading zeros, the current deposit amount is returned unchanged. 9 | * - If the input is cleared, it returns an empty string. 10 | * - If the input is a valid number, it returns the input as a string to preserve decimal places. 11 | * - If none of the conditions are met, the current deposit amount is returned unchanged. 12 | * 13 | * @param val - The new value entered by the user. 14 | * @param currentDepositAmount - The current deposit amount in the input field. 15 | * @returns The sanitized value to be set in the input field. 16 | */ 17 | export const sanitizeInput = (val: string, currentDepositAmount: string): string => { 18 | // If the user presses the decimal point with an empty input, set the value to "0." 19 | if (val === ".") { 20 | return "0."; 21 | } 22 | 23 | // Replace single leading zero with the number pressed (if it's between 1-9) 24 | if (currentDepositAmount === "0" && parseInt(val) > 0 && parseInt(val) <= 9) { 25 | return val.replace("0", ""); 26 | } 27 | 28 | // Allow the input to be an empty string or a valid number format 29 | if (/^\d*\.?\d*$/.test(val) || val === "") { 30 | // Check if the value has multiple leading zeros 31 | if (/^0\d+/.test(val)) { 32 | return currentDepositAmount; // Do nothing if the value starts with multiple leading zeros 33 | } 34 | 35 | // If the value is empty, return an empty string 36 | if (val === "") { 37 | return ""; // Set to empty string when cleared 38 | } else { 39 | // Convert the value to a number for validation 40 | const numberValue = parseFloat(val); 41 | 42 | // If the value is a valid number, return it 43 | if (!isNaN(numberValue)) { 44 | return val; // Keep the value as a string to maintain decimal places 45 | } 46 | } 47 | } 48 | 49 | return currentDepositAmount; // Return the current value if no changes are made 50 | }; 51 | -------------------------------------------------------------------------------- /app/mint-tusd/utils/sanitizeInput.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitizes the deposit input by enforcing specific formatting rules: 3 | * 4 | * - If the user presses the decimal point with an empty input, the value is set to "0.". 5 | * - If the current deposit amount is "0" and the user enters a number between 1-9, 6 | * the leading zero is removed. 7 | * - The input can be an empty string or a valid number format (digits and an optional decimal point). 8 | * - If the input starts with multiple leading zeros, the current deposit amount is returned unchanged. 9 | * - If the input is cleared, it returns an empty string. 10 | * - If the input is a valid number, it returns the input as a string to preserve decimal places. 11 | * - If none of the conditions are met, the current deposit amount is returned unchanged. 12 | * 13 | * @param val - The new value entered by the user. 14 | * @param currentDepositAmount - The current deposit amount in the input field. 15 | * @returns The sanitized value to be set in the input field. 16 | */ 17 | export const sanitizeInput = (val: string, currentDepositAmount: string): string => { 18 | // If the user presses the decimal point with an empty input, set the value to "0." 19 | if (val === ".") { 20 | return "0."; 21 | } 22 | 23 | // Replace single leading zero with the number pressed (if it's between 1-9) 24 | if (currentDepositAmount === "0" && parseInt(val) > 0 && parseInt(val) <= 9) { 25 | return val.replace("0", ""); 26 | } 27 | 28 | // Allow the input to be an empty string or a valid number format 29 | if (/^\d*\.?\d*$/.test(val) || val === "") { 30 | // Check if the value has multiple leading zeros 31 | if (/^0\d+/.test(val)) { 32 | return currentDepositAmount; // Do nothing if the value starts with multiple leading zeros 33 | } 34 | 35 | // If the value is empty, return an empty string 36 | if (val === "") { 37 | return ""; // Set to empty string when cleared 38 | } else { 39 | // Convert the value to a number for validation 40 | const numberValue = parseFloat(val); 41 | 42 | // If the value is a valid number, return it 43 | if (!isNaN(numberValue)) { 44 | return val; // Keep the value as a string to maintain decimal places 45 | } 46 | } 47 | } 48 | 49 | return currentDepositAmount; // Return the current value if no changes are made 50 | }; 51 | -------------------------------------------------------------------------------- /app/components/Deposit/LrtPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Link from "next/link"; 3 | import { Cross, ArrowUpRight } from "../icons"; 4 | 5 | const LrtPopup: React.FC = () => { 6 | const [lrtVisible, setLrtVisible] = useState(false); 7 | 8 | useEffect(() => { 9 | const popupClosed = localStorage.getItem("lrtPopup"); 10 | if (popupClosed === null) { 11 | setLrtVisible(true); 12 | } 13 | }, []); 14 | 15 | const closePopup = () => { 16 | setLrtVisible(false); 17 | localStorage.setItem("lrtPopup", "true"); 18 | }; 19 | 20 | return ( 21 | <> 22 | {lrtVisible && ( 23 | <> 24 |
25 |
26 | 30 | Deposit your LRTs to earn restaked yield 31 | 32 | 33 |
34 | 40 | Deposit Now 41 | 42 | 43 |
44 | 45 |
46 |
47 | LRT image. 52 |
53 |
54 |
55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 | 63 | )} 64 | 65 | ); 66 | }; 67 | 68 | export default LrtPopup; 69 | -------------------------------------------------------------------------------- /app/providers/RelayKitProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { MAINNET_RELAY_API, RelayChain } from "@reservoir0x/relay-sdk"; 3 | import { 4 | RelayKitProvider as _RelayKitProvider, 5 | RelayKitTheme, 6 | } from "@reservoir0x/relay-kit-ui"; 7 | import { type ReactNode } from "react"; 8 | const theme: RelayKitTheme = { 9 | font: "var(--font-ibm-pex-sans)", 10 | primaryColor: "rgb(161, 254, 160)", 11 | focusColor: "rgb(161, 254, 160)", 12 | text: { 13 | default: "#fff", 14 | subtle: "rgba(255, 255, 255, 0.3)", 15 | }, 16 | buttons: { 17 | disabled: { 18 | color: "#5a5a5a", 19 | background: "#0e0e0e", 20 | }, 21 | primary: { 22 | color: "#000", 23 | background: "rgb(163, 255, 165)", 24 | hover: { 25 | color: "#000", 26 | background: "rgb(116, 255, 113)", 27 | }, 28 | }, 29 | secondary: { 30 | color: "#fff", 31 | background: "rgb(28, 28, 28)", 32 | hover: { 33 | color: "#fff", 34 | background: "rgb(35, 35, 35)", 35 | }, 36 | }, 37 | }, 38 | anchor: { 39 | color: "rgba(255, 255, 255, 0.3)", 40 | hover: { 41 | color: "rgba(255, 255, 255, 0.6)", 42 | }, 43 | }, 44 | widget: { 45 | swapCurrencyButtonBorderColor: "rgba(30, 30, 30, 1)", 46 | swapCurrencyButtonBorderRadius: "100%", 47 | swapCurrencyButtonBorderWidth: "1px", 48 | background: "rgba(13, 13, 13, 1)", 49 | borderRadius: "10", 50 | card: { 51 | border: "1px solid rgba(255, 255, 255, 0.1)", 52 | borderRadius: "10px", 53 | gutter: "20px", 54 | }, 55 | }, 56 | }; 57 | export const RelayKitProvider = (props: { 58 | children: ReactNode; 59 | chains: RelayChain[]; 60 | }) => { 61 | return ( 62 | <_RelayKitProvider 63 | options={{ 64 | baseApiUrl: MAINNET_RELAY_API, 65 | chains: props.chains, 66 | pollingInterval: 3000, 67 | logLevel: 4, 68 | disablePoweredByReservoir: true, 69 | source: "bridge.eclipse.xyz", 70 | duneConfig: { 71 | apiKey: process.env.NEXT_PUBLIC_DUNE_API_KEY, 72 | }, 73 | vmConnectorKeyOverrides: { 74 | 9286185: [ 75 | "backpacksol", 76 | "nightlysol", 77 | "okxsolana", 78 | "bybitwalletsol", 79 | "bitgetwalletsol", 80 | "fordefisol", 81 | "gatewalletsol" 82 | ], 83 | }, 84 | }} 85 | theme={theme} 86 | > 87 | {props.children} 88 | 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /lib/relay.ts: -------------------------------------------------------------------------------- 1 | import { RelayChain } from "@reservoir0x/relay-sdk"; 2 | import { GenericNetwork } from "@dynamic-labs/types"; 3 | import { Wallet } from "@dynamic-labs/sdk-react-core"; 4 | import { LinkedWallet } from "@reservoir0x/relay-kit-ui"; 5 | 6 | const extractWalletIcon = (wallet: Wallet) => { 7 | const dynamicStaticAssetUrl = 8 | "https://iconic.dynamic-static-assets.com/icons/sprite.svg"; 9 | //@ts-ignore 10 | const walletBook = wallet?.connector?.walletBook?.wallets; 11 | let walletLogoId = 12 | // @ts-ignore 13 | (wallet?.connector?.wallet?.brand?.spriteId ?? 14 | (walletBook && 15 | wallet.key && 16 | walletBook[wallet.key] && 17 | walletBook[wallet.key].brand && 18 | walletBook[wallet.key].brand.spriteId)) 19 | ? walletBook[wallet.key].brand.spriteId 20 | : undefined; 21 | 22 | // @ts-ignore 23 | let walletIcon = wallet?.connector?.wallet?.icon; 24 | 25 | if (walletLogoId) { 26 | return `${dynamicStaticAssetUrl}#${walletLogoId}`; 27 | } else if (walletIcon) { 28 | return walletIcon; 29 | } else { 30 | return undefined; 31 | } 32 | }; 33 | 34 | export const convertRelayChainToDynamicNetwork = ( 35 | chain: RelayChain, 36 | ): GenericNetwork => { 37 | return { 38 | blockExplorerUrls: [chain.explorerUrl ?? "https://etherscan.io"], 39 | chainId: chain.id, 40 | chainName: chain.name, 41 | iconUrls: 42 | chain.icon?.light || chain.icon?.dark 43 | ? [chain.icon?.light ?? "", chain.icon?.dark ?? ""] 44 | : [], 45 | name: chain.name, 46 | nativeCurrency: { 47 | decimals: chain.currency?.decimals ?? 18, 48 | name: chain.currency?.name ?? "ETH", 49 | symbol: chain.currency?.symbol ?? "ETH", 50 | }, 51 | networkId: chain.id, 52 | rpcUrls: chain.httpRpcUrl ? [chain.httpRpcUrl] : [], 53 | vanityName: chain.displayName, 54 | key: chain.name, 55 | isTestnet: false, 56 | }; 57 | }; 58 | 59 | export const convertToLinkedWallet = (wallet: Wallet): LinkedWallet => { 60 | const walletIcon = extractWalletIcon(wallet); 61 | let walletChain = wallet.chain.toLowerCase(); 62 | let vmType: "evm" | "svm" | "bvm" = "evm"; 63 | 64 | if (walletChain === "sol" || walletChain === "eclipse") { 65 | vmType = "svm"; 66 | } else if (walletChain === "btc") { 67 | vmType = "bvm"; 68 | } 69 | 70 | const address = 71 | wallet.additionalAddresses.find((address) => address.type !== "ordinals") 72 | ?.address ?? wallet.address; 73 | 74 | return { 75 | address, 76 | walletLogoUrl: walletIcon, 77 | vmType, 78 | connector: wallet.connector.key, 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /app/mint-tusd/components/MintAndRedeem.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import { useState } from "react"; 3 | import { Mint } from "./Mint"; 4 | import { Redeem } from "./Redeem"; 5 | import "./styles.css"; 6 | import { NucleusActivityContent } from "./NucleusActivityContent"; 7 | import { Activity } from "@/app/components/icons"; 8 | import { useTransactions } from "../hooks/useTransactions"; 9 | 10 | export enum Tabs { 11 | Mint, 12 | Redeem, 13 | Activity, 14 | } 15 | 16 | function MintAndRedeem() { 17 | const [activeTab, setActiveTab] = useState(Tabs.Redeem); 18 | const { transactions, isLoading } = useTransactions(); 19 | 20 | return ( 21 | <> 22 |
23 |
24 |
25 |
26 |
setActiveTab(Tabs.Mint)} 33 | > 34 | Mint 35 |
36 |
setActiveTab(Tabs.Redeem)} 43 | > 44 | Redeem 45 |
46 |
{ 55 | setActiveTab(Tabs.Activity); 56 | }} 57 | > 58 | 59 |
60 |
61 | {activeTab === Tabs.Mint && } 62 | {activeTab === Tabs.Redeem && } 63 | {activeTab === Tabs.Activity && ( 64 | 68 | )} 69 |
70 |
71 |
72 | 73 | ); 74 | } 75 | 76 | export default MintAndRedeem; 77 | -------------------------------------------------------------------------------- /app/mint-teth/components/MintAndRedeem.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import { useState } from "react"; 3 | import { Mint } from "./Mint"; 4 | import { Redeem } from "./Redeem"; 5 | import "./styles.css"; 6 | import { NucleusActivityContent } from "./NucleusActivityContent"; 7 | import { Activity } from "@/app/components/icons"; 8 | import { useTransactions } from "../hooks/useTransactions"; 9 | 10 | export enum Tabs { 11 | Mint, 12 | Redeem, 13 | Activity, 14 | } 15 | 16 | function MintAndRedeem() { 17 | const [activeTab, setActiveTab] = useState(Tabs.Redeem); 18 | const { transactions, isLoading } = useTransactions(); 19 | 20 | return ( 21 | <> 22 |
23 |
24 |
25 |
26 |
setActiveTab(Tabs.Mint)} 33 | > 34 | Mint 35 |
36 |
setActiveTab(Tabs.Redeem)} 43 | > 44 | Redeem 45 |
46 |
{ 56 | setActiveTab(Tabs.Activity); 57 | }} 58 | > 59 | 60 |
61 |
62 | {activeTab === Tabs.Mint && } 63 | {activeTab === Tabs.Redeem && } 64 | {activeTab === Tabs.Activity && ( 65 | 69 | )} 70 |
71 |
72 |
73 | 74 | ); 75 | } 76 | 77 | export default MintAndRedeem; 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eclipse-bridge", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "NODE_OPTIONS='--max-old-space-size=4096' next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "typecheck": "tsc --noEmit --incremental --watch" 11 | }, 12 | "resolutions": { 13 | "@solana/web3.js": "1.98.0" 14 | }, 15 | "dependencies": { 16 | "@coral-xyz/anchor": "0.30.1", 17 | "@dynamic-labs/bitcoin": "4.8.2", 18 | "@dynamic-labs/eclipse": "4.8.2", 19 | "@dynamic-labs/ethereum": "4.8.2", 20 | "@dynamic-labs/ethereum-core": "4.8.2", 21 | "@dynamic-labs/ethers-v5": "^2.6.2", 22 | "@dynamic-labs/sdk-react-core": "4.8.2", 23 | "@dynamic-labs/solana": "4.8.2", 24 | "@dynamic-labs/utils": "4.8.2", 25 | "@dynamic-labs/solana-core": "4.8.2", 26 | "@dynamic-labs/wagmi-connector": "4.8.2", 27 | "@dynamic-labs/wallet-book": "4.8.2", 28 | "@dynamic-labs/wallet-connector-core": "4.8.2", 29 | "@hyperlane-xyz/registry": "15.3.0", 30 | "@hyperlane-xyz/sdk": "^8.6.1", 31 | "@project-serum/anchor": "^0.26.0", 32 | "@reservoir0x/relay-bitcoin-wallet-adapter": "^1.0.7", 33 | "@reservoir0x/relay-kit-hooks": "1.9.2", 34 | "@reservoir0x/relay-kit-ui": "2.10.11", 35 | "@reservoir0x/relay-sdk": "1.6.15", 36 | "@reservoir0x/relay-svm-wallet-adapter": "4.0.15", 37 | "@sentry/nextjs": "^9.12.0", 38 | "@sentry/replay": "^7.116.0", 39 | "@solana/spl-token": "^0.4.9", 40 | "@solana/web3.js": "1.98.0", 41 | "@tanstack/react-query": "^5.51.21", 42 | "@types/node": "20.8.7", 43 | "@types/react": "18.2.21", 44 | "@types/react-dom": "18.2.7", 45 | "autoprefixer": "10.4.15", 46 | "classnames": "^2.5.1", 47 | "decimal.js": "^10.4.3", 48 | "encoding": "^0.1.13", 49 | "eslint": "8.51.0", 50 | "eslint-config-next": "13.5.6", 51 | "ethers": "^5.7.2", 52 | "framer-motion": "^11.11.8", 53 | "history": "^5.3.0", 54 | "lossless-json": "^4.0.2", 55 | "motion-number": "^0.1.7", 56 | "next": "^15.2.4", 57 | "os-browserify": "^0.3.0", 58 | "path-browserify": "^1.0.1", 59 | "permissionless": "^0.2.26", 60 | "pino-pretty": "^11.2.2", 61 | "postcss": "8.4.31", 62 | "react": "18.2.0", 63 | "react-dom": "18.2.0", 64 | "react-loading-skeleton": "^3.4.0", 65 | "react-router-dom": "^7.1.1", 66 | "react-toastify": "^10.0.6", 67 | "stream-browserify": "^3.0.0", 68 | "tailwind-merge": "^2.5.5", 69 | "tailwindcss": "3.3.3", 70 | "typescript": "5.2.2", 71 | "viem": "^2.21.10", 72 | "wagmi": "^2.9.8" 73 | }, 74 | "devDependencies": { 75 | "postcss-nesting": "^13.0.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/components/icons/sidebar-icons/scan.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ScanIcon = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default ScanIcon; 20 | -------------------------------------------------------------------------------- /app/providers/wagmiProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ReactNode, useEffect, useState } from "react"; 3 | import { http, HttpTransport } from "viem"; 4 | import { WagmiProvider as _WagmiProvider, Config, createConfig } from "wagmi"; 5 | import { Chain, mainnet } from "wagmi/chains"; 6 | import { queryRelayChains } from "@reservoir0x/relay-kit-hooks"; 7 | import { 8 | configureViemChain, 9 | convertViemChainToRelayChain, 10 | MAINNET_RELAY_API, 11 | RelayChain, 12 | } from "@reservoir0x/relay-sdk"; 13 | export type ChildrenProps = { 14 | chains: RelayChain[]; 15 | }; 16 | type WagmiProviderProps = { 17 | children: (props: ChildrenProps) => ReactNode; 18 | }; 19 | 20 | const { wagmiConfig: defaultConfig } = createWagmiConfig([mainnet]); 21 | 22 | export const WagmiProvider = (props: WagmiProviderProps) => { 23 | const [config, setConfig] = useState(defaultConfig); 24 | const [chains, setChains] = useState([ 25 | convertViemChainToRelayChain(mainnet), 26 | ]); 27 | useEffect(() => { 28 | queryRelayChains(MAINNET_RELAY_API, {}).then((data) => { 29 | const eclipseChain = data.chains?.find((chain) => chain.id === 9286185); 30 | if (eclipseChain) { 31 | eclipseChain.explorerUrl = "https://eclipsescan.xyz"; 32 | } 33 | const apiChains = 34 | data.chains 35 | ?.map((chain) => configureViemChain(chain as any)) 36 | //filter out solana temporarily 37 | .filter((chain) => chain.id !== 792703809) ?? []; 38 | const { wagmiConfig } = createWagmiConfig( 39 | apiChains 40 | .filter(({ viemChain }) => viemChain !== undefined) 41 | .map(({ viemChain }) => viemChain as Chain), 42 | ); 43 | setConfig(wagmiConfig); 44 | setChains(apiChains); 45 | }); 46 | }, []); 47 | 48 | return ( 49 | <_WagmiProvider config={config}> 50 | {props.children({ chains: chains ?? [] })} 51 | 52 | ); 53 | }; 54 | function createWagmiConfig(dynamicChains: Chain[]) { 55 | const chains = (dynamicChains.length === 0 ? [mainnet] : dynamicChains) as [ 56 | Chain, 57 | ...Chain[], 58 | ]; 59 | const wagmiConfig = createConfig({ 60 | chains: chains, 61 | ssr: true, 62 | multiInjectedProviderDiscovery: false, 63 | transports: chains.reduce( 64 | (transportsConfig: Record, chain) => { 65 | //TODO: add alchemy transport if needed 66 | if (chain.id === 1) { 67 | transportsConfig[chain.id] = http( 68 | "https://empty-responsive-patron.quiknode.pro/91dfa8475605dcdec9afdc8273578c9f349774a1/", 69 | ); 70 | } else { 71 | transportsConfig[chain.id] = http(); 72 | } 73 | return transportsConfig; 74 | }, 75 | {}, 76 | ), 77 | }); 78 | return { 79 | wagmiConfig, 80 | chains, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /app/mint-teth/components/RedeemSummaryCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { tokenOptions } from "../constants/tokens"; 3 | import { formatUnits } from "viem"; 4 | import Image from "next/image"; 5 | import "./styles.css"; 6 | import { Tooltip } from "./Tooltip"; 7 | 8 | interface RedeemSummaryCardProps { 9 | depositAsset: string; 10 | exchangeRate: string; 11 | withdrawFee: string; 12 | totalFees: string; 13 | bridgeFee: string; 14 | slippage: string; 15 | } 16 | 17 | export function RedeemSummaryCard({ 18 | depositAsset, 19 | exchangeRate, 20 | withdrawFee, 21 | totalFees, 22 | bridgeFee, 23 | slippage, 24 | }: RedeemSummaryCardProps) { 25 | const depositAssetSymbol = tokenOptions.find((token) => token.value === depositAsset)?.label; 26 | const tEthValue = exchangeRate ? 1 / Number(formatUnits(BigInt(exchangeRate), 18)) : 0; 27 | const formattedExchangeRate = `1 ${depositAssetSymbol} = ${tEthValue.toFixed(3)} tETH`; 28 | 29 | return ( 30 |
31 |
32 |
33 |

{formattedExchangeRate}

34 |
35 |
36 |

Total Cost

37 |

{totalFees}

38 |
39 |
40 |

Slippage

41 | 42 |
43 |
44 |

{(parseFloat(slippage) * 100).toFixed(2)}%

45 |
46 |
47 |

Bridge Fee

48 | 49 |
50 |
51 |

{bridgeFee}

52 |
53 |
54 |

Deadline

55 | 56 |
57 |
58 |

7 Days

59 |
60 |
61 |
62 |

Powered by

63 | nucleus 64 |

Nucleus

65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/components/Deposit/activity.css: -------------------------------------------------------------------------------- 1 | .activity-container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 8px; 5 | font-weight: 500; 6 | width: 100%; 7 | height: 364px; 8 | overflow-y: scroll; 9 | scrollbar-width: thin; 10 | scrollbar-color: rgba(33, 33, 33, 1) rgba(255, 255, 255, 0); 11 | 12 | padding-right: 12px; 13 | } 14 | 15 | .deposit-transaction { 16 | padding: 16px 15px 16px 15px; 17 | width: 100%; 18 | height: 85px; 19 | 20 | border: 1px solid rgba(255, 255, 255, 0.1); 21 | border-radius: 10px; 22 | background: rgba(255, 255, 255, 0.03); 23 | transition: background 100ms var(--ease-out-quad); 24 | span::first-letter { 25 | text-transform: uppercase; 26 | } 27 | } 28 | 29 | .deposit-transaction:hover { 30 | background: rgba(255, 255, 255, 0.04); 31 | cursor: pointer; 32 | } 33 | 34 | .white-in { 35 | font-size: 20px; 36 | color: white; 37 | line-height: 26px; 38 | } 39 | 40 | .gray-in { 41 | color: rgba(85, 85, 85, 1); 42 | font-size: 16px; 43 | line-height: 20.8px; 44 | } 45 | 46 | .status-div { 47 | padding: 3px 8px 3px 5px; 48 | gap: 6px; 49 | border-radius: 13px; 50 | font-size: 12px; 51 | } 52 | 53 | .status-div.failed, 54 | .status-div.canceled { 55 | background: rgba(235, 77, 77, 0.05); 56 | color: rgba(235, 77, 77, 1); 57 | } 58 | 59 | .status-div.loading, 60 | .status-div.pending { 61 | background: rgba(255, 255, 255, 0.05); 62 | color: rgba(255, 255, 255, 0.3); 63 | svg { 64 | width: 16px; 65 | } 66 | } 67 | 68 | .status-div.completed, 69 | .status-div.fulfilled { 70 | background: rgba(161, 254, 160, 0.05); 71 | color: rgba(161, 254, 160, 1); 72 | } 73 | 74 | .status-overlay { 75 | position: absolute; 76 | top: 0; 77 | left: 0; 78 | width: 100%; 79 | height: 100%; 80 | transition: opacity 300ms var(--ease-out-quad); 81 | backdrop-filter: blur(3px); 82 | z-index: 1; 83 | opacity: 0; 84 | pointer-events: none; 85 | } 86 | 87 | .status-overlay.active { 88 | z-index: 1; 89 | opacity: 1; 90 | } 91 | 92 | @media (max-width: 500px) { 93 | .white-in { 94 | font-size: 16px; 95 | } 96 | .gray-in { 97 | font-size: 14px; 98 | } 99 | .status-div span { 100 | font-size: 10px; 101 | } 102 | .eth-to-ecl { 103 | gap: 8px !important; 104 | } 105 | .tx-age { 106 | gap: 4px; 107 | } 108 | .deposit-transaction { 109 | padding: 12px 10px 12px 10px; 110 | } 111 | .deposit-transaction img { 112 | width: 39px !important; 113 | height: 39px !important; 114 | } 115 | } 116 | 117 | @keyframes bounce { 118 | 0% { 119 | transform: translateY(-10px); 120 | } 121 | 50% { 122 | transform: translateY(5px); 123 | } 124 | 100% { 125 | transform: translateY(0); 126 | } 127 | } 128 | 129 | .deposit-transaction .preserve-casing::first-letter { 130 | text-transform: none; 131 | } 132 | -------------------------------------------------------------------------------- /app/mint-tusd/components/RedeemSummaryCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { tokenOptions } from "../constants/tokens"; 3 | import { formatUnits } from "viem"; 4 | import Image from "next/image"; 5 | import "./styles.css"; 6 | import { Tooltip } from "./Tooltip"; 7 | 8 | interface RedeemSummaryCardProps { 9 | depositAsset: string; 10 | exchangeRate: string; 11 | withdrawFee: string; 12 | totalFees: string; 13 | bridgeFee: string; 14 | slippage: string; 15 | } 16 | 17 | export function RedeemSummaryCard({ 18 | depositAsset, 19 | exchangeRate, 20 | withdrawFee, 21 | totalFees, 22 | bridgeFee, 23 | slippage, 24 | }: RedeemSummaryCardProps) { 25 | const depositAssetSymbol = tokenOptions.find( 26 | (token) => token.value === depositAsset, 27 | )?.label; 28 | const tEthValue = exchangeRate 29 | ? 1 / Number(formatUnits(BigInt(exchangeRate), 6)) 30 | : 0; 31 | const formattedExchangeRate = `1 ${depositAssetSymbol} = ${tEthValue.toFixed(3)} tUSD`; 32 | 33 | return ( 34 |
35 |
36 |
37 |

{formattedExchangeRate}

38 |
39 |
40 |

Total Cost

41 |

{totalFees}

42 |
43 |
44 |

Slippage

45 | 46 |
47 |
48 |

49 | {(parseFloat(slippage) * 100).toFixed(2)}% 50 |

51 |
52 |
53 |

Bridge Fee

54 | 55 |
56 |
57 |

{bridgeFee}

58 |
59 |
60 |

Deadline

61 | 62 |
63 |
64 |

7 Days

65 |
66 |
67 |
68 |

Powered by

69 | nucleus 70 |

Nucleus

71 |
72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /app/mint-tusd/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { NetworkProvider } from "@/app/contexts/NetworkContext"; 3 | import { useSidebar } from "@/app/contexts/SidebarContext"; 4 | import useEthereumData from "@/lib/ethUtils"; 5 | import { useState } from "react"; 6 | import { SkeletonTheme } from "react-loading-skeleton"; 7 | import { Footer } from "../components/Footer"; 8 | import { Header } from "../components/Header"; 9 | import { TosClickwrap } from "../components/TosClickwrap"; 10 | import MintAndRedeem from "./components/MintAndRedeem"; 11 | import { Sidebar } from "../components/Sidebar"; 12 | import { TransactionProvider } from "../components/TransactionPool"; 13 | import { EthereumDataContext, WalletClientContext } from "../context"; 14 | import { useWalletClient } from "../hooks"; 15 | import { Options } from "@/lib/networkUtils"; 16 | import { ToastContainer } from "react-toastify"; 17 | 18 | import "react-toastify/dist/ReactToastify.min.css"; 19 | 20 | export default function Main() { 21 | const { isSidebar, setIsSidebar } = useSidebar(); 22 | const [selectedOption, setSelectedOption] = useState(Options.Mainnet); 23 | const { gasPrice, ethPrice, blockNumber } = useEthereumData(selectedOption); 24 | const walletClient = useWalletClient(); 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 |
41 |
42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 62 |
63 |
64 |
65 |
66 |
67 | ); 68 | } -------------------------------------------------------------------------------- /app/mint-teth/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { NetworkProvider } from "@/app/contexts/NetworkContext"; 3 | import { useSidebar } from "@/app/contexts/SidebarContext"; 4 | import useEthereumData from "@/lib/ethUtils"; 5 | import { useState } from "react"; 6 | import { SkeletonTheme } from "react-loading-skeleton"; 7 | import { Footer } from "../components/Footer"; 8 | import { Header } from "../components/Header"; 9 | import { TosClickwrap } from "../components/TosClickwrap"; 10 | import MintAndRedeem from "./components/MintAndRedeem"; 11 | import { Sidebar } from "../components/Sidebar"; 12 | import { TransactionProvider } from "../components/TransactionPool"; 13 | import { EthereumDataContext, WalletClientContext } from "../context"; 14 | import { useWalletClient } from "../hooks"; 15 | import { Options } from "@/lib/networkUtils"; 16 | import { ToastContainer } from "react-toastify"; 17 | 18 | import "react-toastify/dist/ReactToastify.min.css"; 19 | 20 | export default function Main() { 21 | const { isSidebar, setIsSidebar } = useSidebar(); 22 | const [selectedOption, setSelectedOption] = useState(Options.Mainnet); 23 | const { gasPrice, ethPrice, blockNumber } = useEthereumData(selectedOption); 24 | const walletClient = useWalletClient(); 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 |
41 |
42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 62 |
63 |
64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/components/Deposit/TapPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Link from "next/link"; 3 | import { Cross, ArrowUpRight, GrassIcon, PassiveGrassBanner } from "../icons"; 4 | 5 | const TapPopup: React.FC = () => { 6 | const [isTapPopupVisible, setIsTapPopupVisible] = useState(false); 7 | 8 | const closePopup = () => { 9 | setIsTapPopupVisible(false); 10 | }; 11 | 12 | return ( 13 | <> 14 | {isTapPopupVisible && ( 15 | <> 16 |
25 |
26 | 32 | Bridge Now To 33 | 34 | 40 |
41 |

Grow Grass

42 | 43 |
44 |
45 | 46 |
47 | 53 | Start Tapping 54 | 55 | 56 |
57 | 58 |
59 |
60 | 65 | 70 |
71 |
72 | 73 |
74 |
75 | 76 | )} 77 | 78 | ); 79 | }; 80 | 81 | export default TapPopup; 82 | -------------------------------------------------------------------------------- /app/mint-tusd/abis/WarpRouteContract.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "contract TellerWithMultiAssetSupport", 7 | "name": "_teller", 8 | "type": "address" 9 | }, 10 | { 11 | "internalType": "contract WarpRoute", 12 | "name": "_warpRoute", 13 | "type": "address" 14 | }, 15 | { 16 | "internalType": "uint32", 17 | "name": "_destination", 18 | "type": "uint32" 19 | } 20 | ], 21 | "stateMutability": "nonpayable", 22 | "type": "constructor" 23 | }, 24 | { 25 | "inputs": [], 26 | "name": "InvalidDestination", 27 | "type": "error" 28 | }, 29 | { 30 | "inputs": [], 31 | "name": "boringVault", 32 | "outputs": [ 33 | { 34 | "internalType": "contract BoringVault", 35 | "name": "", 36 | "type": "address" 37 | } 38 | ], 39 | "stateMutability": "view", 40 | "type": "function" 41 | }, 42 | { 43 | "inputs": [ 44 | { 45 | "internalType": "contract ERC20", 46 | "name": "depositAsset", 47 | "type": "address" 48 | }, 49 | { 50 | "internalType": "uint256", 51 | "name": "depositAmount", 52 | "type": "uint256" 53 | }, 54 | { 55 | "internalType": "uint256", 56 | "name": "minimumMint", 57 | "type": "uint256" 58 | }, 59 | { 60 | "internalType": "bytes32", 61 | "name": "recipient", 62 | "type": "bytes32" 63 | } 64 | ], 65 | "name": "depositAndBridge", 66 | "outputs": [ 67 | { 68 | "internalType": "uint256", 69 | "name": "sharesMinted", 70 | "type": "uint256" 71 | }, 72 | { 73 | "internalType": "bytes32", 74 | "name": "messageId", 75 | "type": "bytes32" 76 | } 77 | ], 78 | "stateMutability": "payable", 79 | "type": "function" 80 | }, 81 | { 82 | "inputs": [], 83 | "name": "destination", 84 | "outputs": [ 85 | { 86 | "internalType": "uint32", 87 | "name": "", 88 | "type": "uint32" 89 | } 90 | ], 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "inputs": [], 96 | "name": "teller", 97 | "outputs": [ 98 | { 99 | "internalType": "contract TellerWithMultiAssetSupport", 100 | "name": "", 101 | "type": "address" 102 | } 103 | ], 104 | "stateMutability": "view", 105 | "type": "function" 106 | }, 107 | { 108 | "inputs": [], 109 | "name": "warpRoute", 110 | "outputs": [ 111 | { 112 | "internalType": "contract WarpRoute", 113 | "name": "", 114 | "type": "address" 115 | } 116 | ], 117 | "stateMutability": "view", 118 | "type": "function" 119 | } 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /app/components/GasStation/SelectToken.tsx: -------------------------------------------------------------------------------- 1 | import { Cross, WalletIcon, CircleCheck } from "../icons"; 2 | import { useTransactionManager, Token } from "./TokenManager" 3 | import { useSidebar } from "@/app/contexts/SidebarContext"; 4 | 5 | 6 | const TokenItem: React.FC<{ token: Token, selectedToken: Token, onClick: () => void}> = ({ token, selectedToken, onClick }) => { 7 | return ( 8 |
9 |
10 | 11 |
12 | { token.symbol } 13 | { token.name } 14 |
15 |
16 | 17 |
18 | 19 | { (Number(token.balance ?? BigInt(0)) / 10 ** token.decimals).toFixed(4) } 20 | { token.symbol === selectedToken.symbol 21 | ? 22 | :
23 | } 24 |
25 | 26 |
27 | ); 28 | } 29 | 30 | export const SelectToken: React.FC<{ 31 | setSelectModal: React.Dispatch>, 32 | setSelectedToken: React.Dispatch>, 33 | selectedToken: Token 34 | }> = ({ setSelectModal, setSelectedToken, selectedToken }) => { 35 | const { tokens } = useTransactionManager(); 36 | const { isSidebar } = useSidebar(); 37 | 38 | return ( 39 |
40 |
41 |
42 | 43 | Choose token to pay with 44 |
{ setSelectModal(false) }}>
45 |
46 |
47 | {Object.keys(tokens).map((key) => { 48 | const token = tokens[key]; 49 | return ( 50 | { setSelectedToken(token); setSelectModal(false) }} 55 | /> 56 | ); 57 | })} 58 |
59 |
60 |
61 | ); 62 | } 63 | 64 | 65 | export default SelectToken; 66 | -------------------------------------------------------------------------------- /app/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Skeleton from "react-loading-skeleton"; 3 | import MotionNumber from "motion-number"; 4 | import { Block, Eth, Gas } from "../icons"; 5 | import { EthereumDataContext } from "@/app/context"; 6 | import { useContext } from "react"; 7 | 8 | export function Footer() { 9 | const [gasPrice, ethPrice, blockNumber] = useContext(EthereumDataContext) ?? [ 10 | null, 11 | null, 12 | null, 13 | ]; 14 | 15 | return ( 16 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /app/components/GasStation/AboutGasStation.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSidebar } from "@/app/contexts/SidebarContext"; 3 | import classNames from "classnames"; 4 | import { Cross } from "../icons"; 5 | import { WarningComponent } from "@/app/components/WarningComponent"; 6 | import Image from "next/image"; 7 | 8 | export interface AboutGasStationProps { 9 | closeModal: () => void; 10 | isOpen: boolean; 11 | } 12 | 13 | export const AboutGasStationComponent: React.FC = ({ 14 | closeModal, 15 | isOpen, 16 | }) => { 17 | const { isSidebar } = useSidebar(); 18 | const modalRef = React.useRef(null); 19 | 20 | const handleBackdropClick = (e: React.MouseEvent) => { 21 | if (modalRef.current && !modalRef.current.contains(e.target as Node)) { 22 | closeModal(); 23 | } 24 | }; 25 | 26 | return ( 27 |
40 | {/* Modal content */} 41 |
e.stopPropagation()} 50 | ref={modalRef} 51 | > 52 |
53 | 54 | 55 | About Gas Station 56 | 57 | 63 |
64 | 65 |
66 |
67 | Gas Station Icon 74 | 75 | No ETH in your wallet? No problem! 76 |
Swap your tokens on Eclipse (like SOL, USDC, or tETH) for 77 | ETH to power your transactions. 78 |
79 |
80 | 85 |
86 |
87 |
88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /app/mint-teth/components/EcSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import Image from "next/image"; 3 | 4 | export interface SelectOption { 5 | value: string; 6 | label: string; 7 | imageSrc: string; 8 | } 9 | 10 | interface TokenSelectProps { 11 | options: SelectOption[] | undefined; 12 | selected: SelectOption | undefined; 13 | disabled?: boolean; 14 | onChange: (val: SelectOption) => void; 15 | smallText?: boolean; 16 | } 17 | 18 | // Eclipse Select 19 | export function EcSelect({ options, selected, disabled, onChange, smallText }: TokenSelectProps) { 20 | const [isOpen, setIsOpen] = useState(false); 21 | 22 | const dropdownRef = useRef(null); 23 | 24 | const handleOptionClick = (option: SelectOption) => { 25 | onChange(option); 26 | setIsOpen(false); 27 | }; 28 | 29 | // Close the dropdown when clicking outside of it 30 | useEffect(() => { 31 | const handleClickOutside = (event: MouseEvent) => { 32 | if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { 33 | setIsOpen(false); 34 | } 35 | }; 36 | 37 | document.addEventListener("mousedown", handleClickOutside); 38 | 39 | return () => { 40 | document.removeEventListener("mousedown", handleClickOutside); 41 | }; 42 | }, []); 43 | 44 | return ( 45 |
46 | 84 | 85 | {isOpen && options?.length && ( 86 |
87 |
88 | {options.map((option) => ( 89 | 93 | ))} 94 |
95 |
96 | )} 97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /app/mint-tusd/components/EcSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import Image from "next/image"; 3 | 4 | export interface SelectOption { 5 | value: string; 6 | label: string; 7 | imageSrc: string; 8 | } 9 | 10 | interface TokenSelectProps { 11 | options: SelectOption[] | undefined; 12 | selected: SelectOption | undefined; 13 | disabled?: boolean; 14 | onChange: (val: SelectOption) => void; 15 | smallText?: boolean; 16 | } 17 | 18 | // Eclipse Select 19 | export function EcSelect({ options, selected, disabled, onChange, smallText }: TokenSelectProps) { 20 | const [isOpen, setIsOpen] = useState(false); 21 | 22 | const dropdownRef = useRef(null); 23 | 24 | const handleOptionClick = (option: SelectOption) => { 25 | onChange(option); 26 | setIsOpen(false); 27 | }; 28 | 29 | // Close the dropdown when clicking outside of it 30 | useEffect(() => { 31 | const handleClickOutside = (event: MouseEvent) => { 32 | if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { 33 | setIsOpen(false); 34 | } 35 | }; 36 | 37 | document.addEventListener("mousedown", handleClickOutside); 38 | 39 | return () => { 40 | document.removeEventListener("mousedown", handleClickOutside); 41 | }; 42 | }, []); 43 | 44 | return ( 45 |
46 | 84 | 85 | {isOpen && options?.length && ( 86 |
87 |
88 | {options.map((option) => ( 89 | 93 | ))} 94 |
95 |
96 | )} 97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /lib/ethUtils.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Options, toOptionsLower } from './networkUtils'; 3 | 4 | interface IEthereumData { 5 | blockNumber: number | null; 6 | gasPrice: number | null; 7 | ethPrice: number | null; 8 | } 9 | 10 | const CACHE_EXPIRATION_MS = 10000; // Cache expiration time (30 sec) 11 | 12 | let cache: IEthereumData & { timestamp: number | null } = { 13 | blockNumber: null, 14 | gasPrice: null, 15 | ethPrice: null, 16 | timestamp: null, 17 | }; 18 | 19 | const useEthereumData = (chain: Options) => { 20 | const [blockNumber, setBlockNumber] = useState(cache.blockNumber); 21 | const [gasPrice, setGasPrice] = useState(cache.gasPrice); 22 | const [ethPrice, setEthPrice] = useState(cache.ethPrice); 23 | const [error, setError] = useState(null); 24 | const [[intervalOption, intervalId], setIntervalMeta] = useState<[Options, NodeJS.Timeout | undefined]>([chain, undefined]) 25 | 26 | useEffect(() => { 27 | if (intervalId === undefined || chain !== intervalOption) { 28 | if (intervalId !== undefined) { 29 | clearInterval(intervalId) 30 | } 31 | 32 | const chainLower = toOptionsLower(chain) 33 | 34 | const fetchData = async () => { 35 | try { 36 | const now = new Date().getTime(); 37 | // only use the cache if chain matches what interval is stored for 38 | if (cache.timestamp && now - cache.timestamp < CACHE_EXPIRATION_MS && chain === intervalOption) { 39 | setBlockNumber(cache.blockNumber); 40 | setGasPrice(cache.gasPrice); 41 | setEthPrice(cache.ethPrice); 42 | return; 43 | } 44 | 45 | console.log('Fetching Ethereum data...'); 46 | const response = await fetch(`/api/ethereum-data?chain=${chainLower}`); 47 | if (!response.ok) { 48 | throw new Error('Failed to fetch Ethereum data'); 49 | } 50 | 51 | const data: IEthereumData = await response.json(); 52 | 53 | if (!data.blockNumber || !data.gasPrice || !data.ethPrice) { 54 | setError('Failed to fetch Ethereum data'); 55 | return; 56 | } 57 | setBlockNumber(data.blockNumber); 58 | setGasPrice(data.gasPrice); 59 | setEthPrice(data.ethPrice); 60 | 61 | cache = { 62 | ...data, 63 | timestamp: now, 64 | }; 65 | } catch (err) { 66 | setError('Failed to fetch Ethereum data'); 67 | } 68 | }; 69 | 70 | fetchData(); 71 | 72 | const interval = setInterval(fetchData, CACHE_EXPIRATION_MS); 73 | setIntervalMeta([chain, interval]) 74 | } 75 | 76 | // return () => { 77 | // clearInterval(intervalId) 78 | // // setIntervalMeta([chain, undefined]) 79 | // } 80 | }, [chain, intervalOption, intervalId]); 81 | 82 | return { blockNumber, gasPrice, ethPrice, error }; 83 | }; 84 | 85 | export default useEthereumData; 86 | -------------------------------------------------------------------------------- /app/gas-station/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import { Header } from "../components/Header"; 4 | import { Sidebar } from "../components/Sidebar"; 5 | import { GasStation } from "../components/GasStation"; 6 | import { Footer } from "../components/Footer"; 7 | import useEthereumData from "@/lib/ethUtils"; 8 | import { TosClickwrap } from "../components/TosClickwrap"; 9 | import { useWalletClient } from "../hooks"; 10 | import { EthereumDataContext, WalletClientContext } from "../context"; 11 | import { 12 | useTransactionManager, 13 | TMProvider, 14 | } from "../components/GasStation/TokenManager"; 15 | import { useSidebar } from "@/app/contexts/SidebarContext"; 16 | import { SkeletonTheme } from "react-loading-skeleton"; 17 | import { Options } from "@/lib/networkUtils"; 18 | import { NetworkProvider } from "../contexts/NetworkContext"; 19 | import { ToastContainer } from "react-toastify"; 20 | 21 | import "react-toastify/dist/ReactToastify.min.css"; 22 | 23 | export default function GasStationPage() { 24 | const [selectedOption, setSelectedOption] = useState(Options.Mainnet); 25 | const { gasPrice, ethPrice, blockNumber } = useEthereumData(selectedOption); 26 | const { isSidebar, setIsSidebar } = useSidebar(); 27 | const walletClient = useWalletClient(); 28 | 29 | return ( 30 | 31 | 35 | 36 | 37 | 38 |
47 |
48 | 49 | 50 |
54 | 58 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 | 78 |
79 |
80 |
81 |
82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import Deposit from "./components/Deposit"; 4 | import { Header } from "./components/Header"; 5 | import { Sidebar } from "./components/Sidebar"; 6 | import { Footer } from "./components/Footer"; 7 | import { TosClickwrap } from "./components/TosClickwrap"; 8 | import useEthereumData from "@/lib/ethUtils"; 9 | import { NetworkProvider } from "@/app/contexts/NetworkContext"; 10 | import { useWalletClient } from "./hooks"; 11 | import { EthereumDataContext, WalletClientContext } from "./context"; 12 | import { useSidebar } from "@/app/contexts/SidebarContext"; 13 | import { ThirdpartyBridgeModalProvider } from "@/app/components/ThirdpartyBridgeModal/ThirdpartyBridgeModalContext"; 14 | import { TransactionProvider } from "./components/TransactionPool"; 15 | import { SkeletonTheme } from "react-loading-skeleton"; 16 | import { Options } from "@/lib/networkUtils"; 17 | import { ToastContainer } from "react-toastify"; 18 | 19 | import "react-toastify/dist/ReactToastify.min.css"; 20 | 21 | export default function Main() { 22 | const { isSidebar, setIsSidebar } = useSidebar(); 23 | const [selectedOption, setSelectedOption] = useState(Options.Mainnet); 24 | const { gasPrice, ethPrice, blockNumber } = useEthereumData(selectedOption); 25 | const [isMobile, setIsMobile] = useState(typeof window !== "undefined" ? window.innerWidth < 768 : false); 26 | const [amountEther, setAmountEther] = useState(undefined); 27 | const walletClient = useWalletClient(); 28 | 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 |
45 |
46 | 47 | 48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /app/api/ethereum-data/route.ts: -------------------------------------------------------------------------------- 1 | import { OptionsLower } from '@/lib/networkUtils'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | 4 | const CACHE_EXPIRATION_MS = 10000; // 10 seconds cache 5 | 6 | interface ICache { 7 | blockNumber: number | null; 8 | gasPrice: number | null; 9 | ethPrice: number | null; 10 | timestamp: number | null; 11 | network: OptionsLower | null 12 | } 13 | 14 | let cache: ICache = { 15 | blockNumber: null, 16 | gasPrice: null, 17 | ethPrice: null, 18 | timestamp: null, 19 | network: null 20 | }; 21 | 22 | let isFetching = false; 23 | 24 | const ETHERSCAN_API_URLS: Record = { 25 | 'mainnet': "https://api.etherscan.io/v2/api", 26 | 'testnet': "https://api.etherscan.io/v2/api" 27 | } 28 | 29 | function isValidChain(c: string): c is OptionsLower { 30 | return c in ETHERSCAN_API_URLS 31 | } 32 | 33 | export async function GET(request: NextRequest) { 34 | const apiKey = process.env.ETHERSCAN_API_KEY || ""; 35 | 36 | if (!apiKey) { 37 | return NextResponse.json({ error: 'API key is not configured' }, { status: 500 }); 38 | } 39 | 40 | const chain = request.nextUrl.searchParams.get('chain')?.toLowerCase() 41 | const parsedChain: OptionsLower = !!chain && isValidChain(chain) ? chain : 'mainnet' 42 | const etherscanAddress = ETHERSCAN_API_URLS[parsedChain] 43 | 44 | const now = new Date().getTime(); 45 | if (cache.timestamp && (now - cache.timestamp) < CACHE_EXPIRATION_MS && cache.network === parsedChain) { 46 | console.log("Using cached data"); 47 | return NextResponse.json(cache); 48 | } 49 | 50 | if (isFetching) { 51 | console.log("Fetch already in progress, returning last known data"); 52 | return NextResponse.json(cache); 53 | } 54 | 55 | try { 56 | isFetching = true; 57 | console.log("Fetching new data from Etherscan"); 58 | 59 | const [blockResponse, gasResponse, priceResponse] = await Promise.all([ 60 | fetch(`${etherscanAddress}?chainid=1&module=proxy&action=eth_blockNumber&apikey=${apiKey}`, {cache: "no-store"}), 61 | fetch(`${etherscanAddress}?chainid=1&module=proxy&action=eth_gasPrice&apikey=${apiKey}`, {cache: "no-store"}), 62 | fetch(`${etherscanAddress}?chainid=1&module=stats&action=ethprice&apikey=${apiKey}`, {cache: "no-store"}) 63 | ]); 64 | 65 | const blockData = await blockResponse.json(); 66 | const gasData = await gasResponse.json(); 67 | const priceData = await priceResponse.json(); 68 | 69 | const parsedGasPrice = parseInt(gasData.result, 16) / 10**9 70 | const gasDecimals = parsedGasPrice < 1 ? 10**4 : 10**2 // if gas price is less than 1 gwei, read 4 decimals; testnet edge case 71 | const newGasPrice = Math.round((parseInt(gasData.result, 16) / 1e9) * gasDecimals) / gasDecimals; 72 | const newBlockNumber = parseInt(blockData.result, 16); 73 | const newEthPrice = Math.round(parseFloat(priceData.result.ethusd) * 100) / 100; 74 | 75 | if (!newBlockNumber || !newGasPrice || !newEthPrice) { 76 | return NextResponse.json(cache); 77 | } 78 | 79 | cache = { 80 | blockNumber: newBlockNumber, 81 | gasPrice: newGasPrice, 82 | ethPrice: newEthPrice, 83 | timestamp: now, 84 | network: parsedChain 85 | }; 86 | 87 | return NextResponse.json(cache); 88 | } catch (error) { 89 | console.error('Error fetching Ethereum data:', error); 90 | return NextResponse.json(cache || { error: 'Failed to fetch Ethereum data' }, { status: 500 }); 91 | } finally { 92 | isFetching = false; 93 | } 94 | } 95 | --------------------------------------------------------------------------------