From 7566fab7737d0a8448892dcb2fd41ad6467ec312 Mon Sep 17 00:00:00 2001 From: Victorien Gauch <85494462+VGau@users.noreply.github.com> Date: Tue, 10 Sep 2024 13:08:37 +0200 Subject: [PATCH] Feat(3102): add menu and history page (#3852) * fix: refactor state management * fix: update pnpm lock file and use fixed version for zustand * feat: add sidebar menu and mobile menu + transaction history page * feat: add side bar menu and history page * fix: remove unused code + update TransactionClaimButton component * fix: update dockerfile to remove warning during build --- bridge-ui/Dockerfile | 2 +- bridge-ui/Makefile | 2 +- bridge-ui/next.config.mjs | 19 + bridge-ui/package.json | 2 +- bridge-ui/playwright.config.ts | 6 +- bridge-ui/src/app/globals.css | 35 - bridge-ui/src/app/page.tsx | 16 +- bridge-ui/src/app/transactions/page.tsx | 27 + bridge-ui/src/assets/icons/bridge.svg | 22 + bridge-ui/src/assets/icons/docs.svg | 13 + bridge-ui/src/assets/icons/faq.svg | 15 + bridge-ui/src/assets/icons/swap.svg | 32 + bridge-ui/src/assets/icons/transaction.svg | 27 + bridge-ui/src/components/ConnectButton.tsx | 14 + .../src/components/bridge/BridgeLayout.tsx | 24 +- bridge-ui/src/components/bridge/BridgeUI.tsx | 2 - .../src/components/bridge/forms/Amount.tsx | 5 +- .../src/components/bridge/forms/Fees.tsx | 5 +- .../components/bridge/forms/TokenModal.tsx | 2 +- bridge-ui/src/components/footer/Footer.tsx | 38 -- bridge-ui/src/components/header/Header.tsx | 55 -- bridge-ui/src/components/hero/Hero.tsx | 60 -- .../src/components/history/HistoryClaim.tsx | 138 ---- .../src/components/history/HistoryItem.tsx | 4 +- bridge-ui/src/components/layouts/Layout.tsx | 33 +- .../src/components/layouts/MobileMenu.tsx | 78 +++ bridge-ui/src/components/layouts/Modal.tsx | 32 + .../src/components/layouts/Providers.tsx | 8 +- bridge-ui/src/components/layouts/Sidebar.tsx | 68 ++ .../{ => layouts}/header/Chains.tsx | 0 .../src/components/layouts/header/Header.tsx | 62 ++ .../{ => layouts}/header/Wallet.tsx | 20 +- .../src/components/shortcut/Shortcut.tsx | 188 ------ bridge-ui/src/components/terms/TermsModal.tsx | 12 +- .../components/transactions/NoTransaction.tsx | 12 + .../components/transactions/StatusText.tsx | 25 + .../transactions/TransactionItem.tsx | 78 +++ .../transactions/TransactionProgressBar.tsx | 137 ++++ .../components/transactions/Transactions.tsx | 93 +++ .../src/components/transactions/index.tsx | 2 + .../modals/TransactionClaimButton.tsx | 93 +++ .../modals/TransactionDetailsModal.tsx | 71 +++ .../src/components/widgets/SwitchNetwork.tsx | 8 +- bridge-ui/src/contexts/modal.context.tsx | 46 ++ bridge-ui/src/data/shortcuts.md | 34 - bridge-ui/src/hooks/index.ts | 7 +- bridge-ui/src/hooks/useBridge.ts | 4 +- .../src/hooks/useFetchBridgeTransactions.ts | 8 +- bridge-ui/src/hooks/useFetchHistory.ts | 12 +- bridge-ui/src/hooks/useInitialiseChain.ts | 6 +- bridge-ui/src/hooks/useInitialiseToken.ts | 27 +- bridge-ui/src/hooks/useLineaSDK.ts | 52 ++ bridge-ui/src/hooks/useMessageService.ts | 277 -------- bridge-ui/src/hooks/useMessageStatus.ts | 102 +++ bridge-ui/src/hooks/useMinimumFee.ts | 47 ++ .../src/hooks/useTransactionManagement.ts | 118 ++++ bridge-ui/src/models/shortcut.ts | 6 - bridge-ui/src/services/fetchERC20Image.ts | 48 -- bridge-ui/src/services/fetchTokenInfo.ts | 67 -- bridge-ui/src/services/index.ts | 1 + bridge-ui/src/services/tokenService.ts | 139 ++++ bridge-ui/src/stores/configStore.ts | 7 + bridge-ui/src/utils/chainsUtil.ts | 13 + bridge-ui/src/utils/constants.ts | 38 ++ bridge-ui/src/utils/format.ts | 22 + .../transactionParsers/parseERC20Events.ts | 2 +- bridge-ui/svg.d.ts | 4 + bridge-ui/tailwind.config.ts | 9 +- bridge-ui/test/wallet-setup/metamask.setup.ts | 2 + pnpm-lock.yaml | 596 ++++++++++++++---- 70 files changed, 2074 insertions(+), 1205 deletions(-) create mode 100644 bridge-ui/src/app/transactions/page.tsx create mode 100644 bridge-ui/src/assets/icons/bridge.svg create mode 100644 bridge-ui/src/assets/icons/docs.svg create mode 100644 bridge-ui/src/assets/icons/faq.svg create mode 100644 bridge-ui/src/assets/icons/swap.svg create mode 100644 bridge-ui/src/assets/icons/transaction.svg create mode 100644 bridge-ui/src/components/ConnectButton.tsx delete mode 100644 bridge-ui/src/components/footer/Footer.tsx delete mode 100644 bridge-ui/src/components/header/Header.tsx delete mode 100644 bridge-ui/src/components/hero/Hero.tsx delete mode 100644 bridge-ui/src/components/history/HistoryClaim.tsx create mode 100644 bridge-ui/src/components/layouts/MobileMenu.tsx create mode 100644 bridge-ui/src/components/layouts/Modal.tsx create mode 100644 bridge-ui/src/components/layouts/Sidebar.tsx rename bridge-ui/src/components/{ => layouts}/header/Chains.tsx (100%) create mode 100644 bridge-ui/src/components/layouts/header/Header.tsx rename bridge-ui/src/components/{ => layouts}/header/Wallet.tsx (75%) delete mode 100644 bridge-ui/src/components/shortcut/Shortcut.tsx create mode 100644 bridge-ui/src/components/transactions/NoTransaction.tsx create mode 100644 bridge-ui/src/components/transactions/StatusText.tsx create mode 100644 bridge-ui/src/components/transactions/TransactionItem.tsx create mode 100644 bridge-ui/src/components/transactions/TransactionProgressBar.tsx create mode 100644 bridge-ui/src/components/transactions/Transactions.tsx create mode 100644 bridge-ui/src/components/transactions/index.tsx create mode 100644 bridge-ui/src/components/transactions/modals/TransactionClaimButton.tsx create mode 100644 bridge-ui/src/components/transactions/modals/TransactionDetailsModal.tsx create mode 100644 bridge-ui/src/contexts/modal.context.tsx delete mode 100644 bridge-ui/src/data/shortcuts.md create mode 100644 bridge-ui/src/hooks/useLineaSDK.ts delete mode 100644 bridge-ui/src/hooks/useMessageService.ts create mode 100644 bridge-ui/src/hooks/useMessageStatus.ts create mode 100644 bridge-ui/src/hooks/useMinimumFee.ts create mode 100644 bridge-ui/src/hooks/useTransactionManagement.ts delete mode 100644 bridge-ui/src/models/shortcut.ts delete mode 100644 bridge-ui/src/services/fetchERC20Image.ts delete mode 100644 bridge-ui/src/services/fetchTokenInfo.ts create mode 100644 bridge-ui/src/services/index.ts create mode 100644 bridge-ui/src/services/tokenService.ts create mode 100644 bridge-ui/src/utils/constants.ts create mode 100644 bridge-ui/svg.d.ts diff --git a/bridge-ui/Dockerfile b/bridge-ui/Dockerfile index ebdacce6..c3d23f0d 100644 --- a/bridge-ui/Dockerfile +++ b/bridge-ui/Dockerfile @@ -33,7 +33,7 @@ ARG X_TAG WORKDIR /app ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED 1 +ENV NEXT_TELEMETRY_DISABLED=1 USER node diff --git a/bridge-ui/Makefile b/bridge-ui/Makefile index f27eb9af..af1677a6 100644 --- a/bridge-ui/Makefile +++ b/bridge-ui/Makefile @@ -6,4 +6,4 @@ sgr0 := $(shell tput sgr0) .PHONY: dev dev: - npm run dev + pnpm run dev diff --git a/bridge-ui/next.config.mjs b/bridge-ui/next.config.mjs index b60e2768..98f13e74 100644 --- a/bridge-ui/next.config.mjs +++ b/bridge-ui/next.config.mjs @@ -35,6 +35,25 @@ const nextConfig = { fs: false, }; config.externals.push("pino-pretty", "lokijs", "encoding"); + + const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.(".svg")); + + config.module.rules.push( + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, + }, + { + test: /\.svg$/i, + issuer: fileLoaderRule.issuer, + resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, + use: ["@svgr/webpack"], + }, + ); + + fileLoaderRule.exclude = /\.svg$/i; + return config; }, }; diff --git a/bridge-ui/package.json b/bridge-ui/package.json index 11798dd1..73959e29 100644 --- a/bridge-ui/package.json +++ b/bridge-ui/package.json @@ -30,7 +30,6 @@ "compare-versions": "6.1.1", "date-fns": "3.6.0", "framer-motion": "11.3.17", - "gray-matter": "4.0.3", "joi": "17.13.3", "loglevel": "1.9.1", "next": "14.2.5", @@ -50,6 +49,7 @@ }, "devDependencies": { "@playwright/test": "1.45.3", + "@svgr/webpack": "^8.1.0", "@synthetixio/synpress": "4.0.0-alpha.7", "@types/fs-extra": "11.0.4", "@types/react": "18.3.3", diff --git a/bridge-ui/playwright.config.ts b/bridge-ui/playwright.config.ts index 6f7bd540..001f5384 100644 --- a/bridge-ui/playwright.config.ts +++ b/bridge-ui/playwright.config.ts @@ -7,11 +7,11 @@ export default defineConfig({ maxFailures: process.env.CI ? 1 : 0, workers: process.env.CI ? 1 : undefined, reporter: process.env.CI - ? [['html', { open: 'never', outputFolder: `playwright-report-${process.env.HEADLESS ? 'headless' : 'headful'}` }]] - : 'html', + ? [["html", { open: "never", outputFolder: `playwright-report-${process.env.HEADLESS ? "headless" : "headful"}` }]] + : "html", use: { baseURL: "http://localhost:3000", - trace: process.env.CI ? 'on' : 'retain-on-failure' + trace: process.env.CI ? "on" : "retain-on-failure", }, projects: [ { diff --git a/bridge-ui/src/app/globals.css b/bridge-ui/src/app/globals.css index 694de178..e90b3459 100644 --- a/bridge-ui/src/app/globals.css +++ b/bridge-ui/src/app/globals.css @@ -3,35 +3,11 @@ @tailwind utilities; :root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - /* toastify */ --toastify-color-success: #7adffd; --toastify-color-progress-success: #7adffd; } -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } -} - -/* -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} -*/ - body { background: #121212; } @@ -45,15 +21,4 @@ body { .btn-custom { @apply min-h-[2.5rem] h-[2.5rem] px-6; } - - - .shortcut-card { - @apply rounded-sm relative transition-all duration-300 ease-in-out px-4 py-5 flex flex-col; - @apply hover:after:bg-primary hover:border-primary hover:-translate-y-0.5 bg-cardBg border border-card; - - &::after { - @apply absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 w-2.5 h-2.5 bg-card rounded-full z-10 transition-all duration-300 ease-in-out; - content: ""; - } - } } diff --git a/bridge-ui/src/app/page.tsx b/bridge-ui/src/app/page.tsx index a2b58aee..5b7a7f45 100644 --- a/bridge-ui/src/app/page.tsx +++ b/bridge-ui/src/app/page.tsx @@ -1,14 +1,10 @@ import BridgeLayout from "@/components/bridge/BridgeLayout"; -import { Shortcut } from "@/models/shortcut"; -import matter from "gray-matter"; - -async function getShortcuts() { - const { data } = matter.read("src/data/shortcuts.md"); - return data as Shortcut[]; -} export default async function Home() { - const shortcuts = await getShortcuts(); - - return ; + return ( +
+

Bridge

+ +
+ ); } diff --git a/bridge-ui/src/app/transactions/page.tsx b/bridge-ui/src/app/transactions/page.tsx new file mode 100644 index 00000000..8cc229f8 --- /dev/null +++ b/bridge-ui/src/app/transactions/page.tsx @@ -0,0 +1,27 @@ +"use client"; + +import ConnectButton from "@/components/ConnectButton"; +import { Transactions } from "@/components/transactions"; +import { useAccount } from "wagmi"; + +export default function TransactionsPage() { + const { isConnected } = useAccount(); + + if (!isConnected) { + return ( +
+
+ Please connect your wallet. + +
+
+ ); + } + + return ( +
+

Transactions

+ +
+ ); +} diff --git a/bridge-ui/src/assets/icons/bridge.svg b/bridge-ui/src/assets/icons/bridge.svg new file mode 100644 index 00000000..5ba15821 --- /dev/null +++ b/bridge-ui/src/assets/icons/bridge.svg @@ -0,0 +1,22 @@ + + + + + + + diff --git a/bridge-ui/src/assets/icons/docs.svg b/bridge-ui/src/assets/icons/docs.svg new file mode 100644 index 00000000..44a6830e --- /dev/null +++ b/bridge-ui/src/assets/icons/docs.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/bridge-ui/src/assets/icons/faq.svg b/bridge-ui/src/assets/icons/faq.svg new file mode 100644 index 00000000..9b57d375 --- /dev/null +++ b/bridge-ui/src/assets/icons/faq.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/bridge-ui/src/assets/icons/swap.svg b/bridge-ui/src/assets/icons/swap.svg new file mode 100644 index 00000000..f5a5136a --- /dev/null +++ b/bridge-ui/src/assets/icons/swap.svg @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/bridge-ui/src/assets/icons/transaction.svg b/bridge-ui/src/assets/icons/transaction.svg new file mode 100644 index 00000000..ba0f1958 --- /dev/null +++ b/bridge-ui/src/assets/icons/transaction.svg @@ -0,0 +1,27 @@ + + + + + + + diff --git a/bridge-ui/src/components/ConnectButton.tsx b/bridge-ui/src/components/ConnectButton.tsx new file mode 100644 index 00000000..f1a5d4e0 --- /dev/null +++ b/bridge-ui/src/components/ConnectButton.tsx @@ -0,0 +1,14 @@ +import { useWeb3Modal } from "@web3modal/wagmi/react"; + +export default function ConnectButton() { + const { open } = useWeb3Modal(); + return ( + + ); +} diff --git a/bridge-ui/src/components/bridge/BridgeLayout.tsx b/bridge-ui/src/components/bridge/BridgeLayout.tsx index ea30a41d..770021ac 100644 --- a/bridge-ui/src/components/bridge/BridgeLayout.tsx +++ b/bridge-ui/src/components/bridge/BridgeLayout.tsx @@ -1,27 +1,7 @@ "use client"; import BridgeUI from "@/components/bridge/BridgeUI"; -import Hero from "@/components/hero/Hero"; -import { UIContext } from "@/contexts/ui.context"; -import { Shortcut } from "@/models/shortcut"; -import { createContext, useContext } from "react"; -const ShortcutContext = createContext([]); - -export function useShortcuts() { - return useContext(ShortcutContext); -} - -export default function BridgeLayout({ shortcuts }: { shortcuts: Shortcut[] }) { - const { showBridge } = useContext(UIContext); - - if (showBridge) { - return ; - } - - return ( - - - - ); +export default function BridgeLayout() { + return ; } diff --git a/bridge-ui/src/components/bridge/BridgeUI.tsx b/bridge-ui/src/components/bridge/BridgeUI.tsx index 9aa32e3a..751cf2b2 100644 --- a/bridge-ui/src/components/bridge/BridgeUI.tsx +++ b/bridge-ui/src/components/bridge/BridgeUI.tsx @@ -4,7 +4,6 @@ import React from "react"; import { useAccount } from "wagmi"; import WrongNetwork from "./WrongNetwork"; import TermsModal from "../terms/TermsModal"; -import History from "@/components/history/History"; import Bridge from "@/components/bridge/forms/Bridge"; import { AnimatePresence, motion } from "framer-motion"; import { NetworkType } from "@/config"; @@ -37,7 +36,6 @@ const BridgeUI: React.FC = () => {
{networkType !== NetworkType.WRONG_NETWORK || !isConnected ? : }
- {isConnected && } {/* */} diff --git a/bridge-ui/src/components/bridge/forms/Amount.tsx b/bridge-ui/src/components/bridge/forms/Amount.tsx index e32a3776..665a14da 100644 --- a/bridge-ui/src/components/bridge/forms/Amount.tsx +++ b/bridge-ui/src/components/bridge/forms/Amount.tsx @@ -5,9 +5,10 @@ import { useFormContext } from "react-hook-form"; import Image from "next/image"; import { useAccount } from "wagmi"; import { formatEther, parseUnits } from "viem"; -import { useBridge, useMessageService } from "@/hooks"; +import { useBridge } from "@/hooks"; import { TokenType } from "@/config"; import { useChainStore } from "@/stores/chainStore"; +import useMinimumFee from "@/hooks/useMinimumFee"; const MAX_AMOUNT_CHAR = 24; const FEES_MARGIN_PERCENT = 20; @@ -35,7 +36,7 @@ export default function Amount({ tokensModalRef }: Props) { // Hooks const { isConnected } = useAccount(); const { estimateGasBridge } = useBridge(); - const { minimumFee } = useMessageService(); + const { minimumFee } = useMinimumFee(); const compareAmountBalance = useCallback( (_amount: string) => { diff --git a/bridge-ui/src/components/bridge/forms/Fees.tsx b/bridge-ui/src/components/bridge/forms/Fees.tsx index 9440645b..6308e95b 100644 --- a/bridge-ui/src/components/bridge/forms/Fees.tsx +++ b/bridge-ui/src/components/bridge/forms/Fees.tsx @@ -7,10 +7,11 @@ import { MdInfoOutline } from "react-icons/md"; import classNames from "classnames"; import { formatEther, parseEther, parseUnits } from "viem"; import { useAccount, useBalance } from "wagmi"; -import { useApprove, useMessageService, useExecutionFee } from "@/hooks"; +import { useApprove, useExecutionFee } from "@/hooks"; import useBridge from "@/hooks/useBridge"; import { NetworkLayer, TokenType } from "@/config"; import { useChainStore } from "@/stores/chainStore"; +import useMinimumFee from "@/hooks/useMinimumFee"; export default function Fees() { const [estimatedGasFee, setEstimatedGasFee] = useState(); @@ -41,7 +42,7 @@ export default function Fees() { const balance = watch("balance", false); // Hooks - const { minimumFee } = useMessageService(); + const { minimumFee } = useMinimumFee(); const { estimateGasBridge } = useBridge(); const { estimateApprove } = useApprove(); const minFees = useExecutionFee({ diff --git a/bridge-ui/src/components/bridge/forms/TokenModal.tsx b/bridge-ui/src/components/bridge/forms/TokenModal.tsx index 450d6242..a12f16f4 100644 --- a/bridge-ui/src/components/bridge/forms/TokenModal.tsx +++ b/bridge-ui/src/components/bridge/forms/TokenModal.tsx @@ -4,12 +4,12 @@ import { useMemo, useState } from "react"; import { isAddress, getAddress } from "viem"; import TokenDetails from "./TokenDetails"; import { NetworkType, TokenInfo, TokenType } from "@/config/config"; -import fetchTokenInfo from "@/services/fetchTokenInfo"; import useERC20Storage from "@/hooks/useERC20Storage"; import { safeGetAddress } from "@/utils/format"; import { useBridge } from "@/hooks"; import { useChainStore } from "@/stores/chainStore"; import { useTokenStore } from "@/stores/tokenStore"; +import { fetchTokenInfo } from "@/services"; interface Props { tokensModalRef: React.RefObject; diff --git a/bridge-ui/src/components/footer/Footer.tsx b/bridge-ui/src/components/footer/Footer.tsx deleted file mode 100644 index 9091350e..00000000 --- a/bridge-ui/src/components/footer/Footer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Link from "next/link"; - -import PackageJSON from "@/../package.json"; - -export default function Footer() { - return ( -
-
- @{new Date().getFullYear()} - - LINEA • A Consensys Formation - - v{PackageJSON.version} -
-
- - Tutorial - - - Documentation - - - Terms of service - -
-
- ); -} diff --git a/bridge-ui/src/components/header/Header.tsx b/bridge-ui/src/components/header/Header.tsx deleted file mode 100644 index 099f2b8a..00000000 --- a/bridge-ui/src/components/header/Header.tsx +++ /dev/null @@ -1,55 +0,0 @@ -"use client"; - -import { useContext } from "react"; -import Image from "next/image"; -import { useAccount } from "wagmi"; -import Wallet from "./Wallet"; -import Chains from "./Chains"; -import { UIContext } from "@/contexts/ui.context"; -import { NetworkType } from "@/config"; -import { useChainStore } from "@/stores/chainStore"; - -export default function Header() { - // Hooks - const { isConnected } = useAccount(); - - // Context - const networkType = useChainStore((state) => state.networkType); - - const { toggleShowBridge } = useContext(UIContext); - - return ( -
-
- - {networkType === NetworkType.SEPOLIA && ( -
TESTNET
- )} -
-
-
    - {isConnected && ( -
  • - -
  • - )} -
  • - -
  • -
-
-
- ); -} diff --git a/bridge-ui/src/components/hero/Hero.tsx b/bridge-ui/src/components/hero/Hero.tsx deleted file mode 100644 index 1e083ebb..00000000 --- a/bridge-ui/src/components/hero/Hero.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; - -import React, { useContext } from "react"; -import { UIContext } from "@/contexts/ui.context"; -import ToolTip from "../toolTip/ToolTip"; -import Shortcut from "../shortcut/Shortcut"; - -const Hero: React.FC = () => { - const { toggleShowBridge } = useContext(UIContext); - return ( - <> -
-

- How would you like
to bridge your funds? -

-
- - Metamask bridge - - - Third-party bridges - - -
-
- - - - ); -}; - -export default Hero; diff --git a/bridge-ui/src/components/history/HistoryClaim.tsx b/bridge-ui/src/components/history/HistoryClaim.tsx deleted file mode 100644 index 847d13d4..00000000 --- a/bridge-ui/src/components/history/HistoryClaim.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useEffect, useState } from "react"; -import { MdCheck } from "react-icons/md"; -import { OnChainMessageStatus } from "@consensys/linea-sdk"; -import classNames from "classnames"; -import { toast } from "react-toastify"; -import { MessageWithStatus } from "@/hooks/useMessageService"; -import { useMessageService, useSwitchNetwork } from "@/hooks"; -import { Transaction } from "@/models"; -import { TransactionHistory } from "@/models/history"; -import { useWaitForTransactionReceipt } from "wagmi"; -import { useChainStore } from "@/stores/chainStore"; - -interface Props { - message: MessageWithStatus; - transaction: TransactionHistory; -} - -export default function HistoryClaim({ message, transaction }: Props) { - const [waitingTransaction, setWaitingTransaction] = useState(); - const [isClaimingLoading, setIsClaimingLoading] = useState(false); - const [isSuccess, setIsSuccess] = useState(false); - - // Is automatic or manual bridging - const manualBridging = message.fee === "0"; - - // Context - const toChain = useChainStore((state) => state.toChain); - - // Hooks - const { switchChainById } = useSwitchNetwork(toChain?.id); - const { writeClaimMessage, isLoading: isTxLoading, transaction: claimTx } = useMessageService(); - - // Wagmi - const { - isLoading: isWaitingLoading, - isSuccess: isWaitingSuccess, - isError: isWaitingError, - } = useWaitForTransactionReceipt({ - hash: waitingTransaction?.txHash, - chainId: waitingTransaction?.chainId, - }); - - const BridgingClaimable = ({ enabled = false }) => { - const claimBusy = isClaimingLoading || isTxLoading || isWaitingLoading || !enabled; - return ( -
- -
- ); - }; - - const BridgingComplete = () => ( -
- - Bridging complete -
- ); - - const WaitingForTransaction = ({ loading }: { loading: boolean }) => ( -
- {loading && } - Please wait, your funds are being bridged -
- ); - - const getClaimStatus = () => { - if (message.status === OnChainMessageStatus.CLAIMED || isSuccess) { - return ; - } else if (message.status === OnChainMessageStatus.CLAIMABLE) { - return ; - } else { - if (manualBridging) { - return ( -
- - -
- ); - } - return ; - } - }; - - const onClaimMessage = async () => { - if (isClaimingLoading) { - return; - } - - try { - setIsClaimingLoading(true); - await switchChainById(transaction.toChain.id); - await writeClaimMessage(message, transaction); - // eslint-disable-next-line no-empty - } catch (error) { - } finally { - setIsClaimingLoading(false); - } - }; - - useEffect(() => { - if (claimTx) { - setWaitingTransaction({ - txHash: claimTx.txHash, - chainId: transaction.toChain.id, - name: transaction.toChain.name, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [claimTx]); - - useEffect(() => { - if (isWaitingSuccess) { - toast.success(`Funds claimed on ${transaction.toChain.name}.`); - setIsSuccess(true); - setWaitingTransaction(undefined); - } - }, [isWaitingSuccess, transaction]); - - useEffect(() => { - if (isWaitingError) { - toast.error("Funds claiming failed."); - setWaitingTransaction(undefined); - } - }, [isWaitingError]); - - return
{getClaimStatus()}
; -} diff --git a/bridge-ui/src/components/history/HistoryItem.tsx b/bridge-ui/src/components/history/HistoryItem.tsx index 55522ca0..1b77e96d 100644 --- a/bridge-ui/src/components/history/HistoryItem.tsx +++ b/bridge-ui/src/components/history/HistoryItem.tsx @@ -7,9 +7,9 @@ import { MdOutlineArrowRightAlt } from "react-icons/md"; import { Address, formatUnits, zeroAddress } from "viem"; import ChainLogo from "@/components/widgets/ChainLogo"; -import HistoryClaim from "./HistoryClaim"; import { formatAddress, safeGetAddress } from "@/utils/format"; import { TransactionHistory } from "@/models/history"; +import TransactionClaimButton from "../transactions/modals/TransactionClaimButton"; interface Props { transaction: TransactionHistory; @@ -112,7 +112,7 @@ export default function HistoryItem({ transaction, variants }: Props) { {transaction.messages?.map((message) => { - return ; + return ; })}
  • diff --git a/bridge-ui/src/components/layouts/Layout.tsx b/bridge-ui/src/components/layouts/Layout.tsx index 8cda7b1b..d33742c2 100644 --- a/bridge-ui/src/components/layouts/Layout.tsx +++ b/bridge-ui/src/components/layouts/Layout.tsx @@ -1,14 +1,13 @@ "use client"; -import Image from "next/image"; import { ToastContainer } from "react-toastify"; import atypTextFont from "@/app/font/atypText"; import atypFont from "@/app/font/atyp"; -import Header from "../header/Header"; +import Header from "./header/Header"; import SwitchNetwork from "../widgets/SwitchNetwork"; -import Footer from "../footer/Footer"; import useInitialiseChain from "@/hooks/useInitialiseChain"; import useInitialiseToken from "@/hooks/useInitialiseToken"; +import Sidebar from "./Sidebar"; export function Layout({ children }: { children: React.ReactNode }) { useInitialiseChain(); @@ -16,7 +15,7 @@ export function Layout({ children }: { children: React.ReactNode }) { return (
    -
    -
    - Linea - Linea + +
    +
    +
    +
    {children} +
    - -
    ); } diff --git a/bridge-ui/src/components/layouts/MobileMenu.tsx b/bridge-ui/src/components/layouts/MobileMenu.tsx new file mode 100644 index 00000000..55c462dc --- /dev/null +++ b/bridge-ui/src/components/layouts/MobileMenu.tsx @@ -0,0 +1,78 @@ +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { MENU_ITEMS } from "@/utils/constants"; +import { MdOutlineClose } from "react-icons/md"; + +type MobileMenuProps = { + toggleMenu: () => void; +}; + +export default function MobileMenu({ toggleMenu }: MobileMenuProps) { + const pathname = usePathname(); + + return ( +
    +
    + Linea logo + +
    +
    +
    +
      + {MENU_ITEMS.map(({ title, href, external, Icon }) => ( +
    • + {external ? ( + + + {title} + + ) : ( + + + {title} + + )} +
    • + ))} +
    +
    +
    + + Contact Support + + + Terms of service + +
    +
    +
    + ); +} diff --git a/bridge-ui/src/components/layouts/Modal.tsx b/bridge-ui/src/components/layouts/Modal.tsx new file mode 100644 index 00000000..bd759dd8 --- /dev/null +++ b/bridge-ui/src/components/layouts/Modal.tsx @@ -0,0 +1,32 @@ +"use client"; + +import classNames from "classnames"; +import { useContext } from "react"; +import { ModalContext } from "@/contexts/modal.context"; +import { MdOutlineClose } from "react-icons/md"; + +type ModalProps = Record; + +const Modal: React.FC = () => { + const { ref, modalContent, options } = useContext(ModalContext); + + const width = options?.width ? options.width : "w-screen md:w-[600px]"; + + return ( + +
    +
    + +
    + {modalContent} +
    +
    + +
    +
    + ); +}; + +export default Modal; diff --git a/bridge-ui/src/components/layouts/Providers.tsx b/bridge-ui/src/components/layouts/Providers.tsx index 29f1695d..129c0c5d 100644 --- a/bridge-ui/src/components/layouts/Providers.tsx +++ b/bridge-ui/src/components/layouts/Providers.tsx @@ -1,8 +1,8 @@ "use client"; import { State } from "wagmi"; -import { UIProvider } from "@/contexts/ui.context"; import { Web3Provider } from "@/contexts/web3.context"; +import { ModalProvider } from "@/contexts/modal.context"; type ProvidersProps = { children: JSX.Element; @@ -11,8 +11,8 @@ type ProvidersProps = { export function Providers({ children, initialState }: ProvidersProps) { return ( - - {children} - + + {children} + ); } diff --git a/bridge-ui/src/components/layouts/Sidebar.tsx b/bridge-ui/src/components/layouts/Sidebar.tsx new file mode 100644 index 00000000..f7bc73bd --- /dev/null +++ b/bridge-ui/src/components/layouts/Sidebar.tsx @@ -0,0 +1,68 @@ +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import Image from "next/image"; +import { MENU_ITEMS } from "@/utils/constants"; + +export default function Sidebar() { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/bridge-ui/src/components/header/Chains.tsx b/bridge-ui/src/components/layouts/header/Chains.tsx similarity index 100% rename from bridge-ui/src/components/header/Chains.tsx rename to bridge-ui/src/components/layouts/header/Chains.tsx diff --git a/bridge-ui/src/components/layouts/header/Header.tsx b/bridge-ui/src/components/layouts/header/Header.tsx new file mode 100644 index 00000000..db3ec0d7 --- /dev/null +++ b/bridge-ui/src/components/layouts/header/Header.tsx @@ -0,0 +1,62 @@ +import { usePathname } from "next/navigation"; +import { useAccount } from "wagmi"; +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { MdMenu } from "react-icons/md"; +import Wallet from "./Wallet"; +import Chains from "./Chains"; +import MobileMenu from "../MobileMenu"; + +function formatPath(pathname: string): string { + switch (pathname) { + case "/": + case "": + return "Bridge"; + case "/transactions": + return "Transactions"; + default: + return ""; + } +} + +export default function Header() { + // Hooks + const { isConnected } = useAccount(); + const pathname = usePathname(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const toggleMenu = () => { + setIsMenuOpen(!isMenuOpen); + }; + + useEffect(() => { + if (isMenuOpen) { + document.body.classList.add("overflow-hidden"); + } else { + document.body.classList.remove("overflow-hidden"); + } + }, [isMenuOpen]); + + return ( +
    + Linea logo +

    {formatPath(pathname)}

    +
    +
      + {isConnected && ( +
    • + +
    • + )} +
    • + +
    • +
    + + {isMenuOpen && } +
    +
    + ); +} diff --git a/bridge-ui/src/components/header/Wallet.tsx b/bridge-ui/src/components/layouts/header/Wallet.tsx similarity index 75% rename from bridge-ui/src/components/header/Wallet.tsx rename to bridge-ui/src/components/layouts/header/Wallet.tsx index 5f1ff3cd..9c3d8689 100644 --- a/bridge-ui/src/components/header/Wallet.tsx +++ b/bridge-ui/src/components/layouts/header/Wallet.tsx @@ -4,20 +4,14 @@ import { useEffect, useRef } from "react"; import Image from "next/image"; import { useAccount, useDisconnect } from "wagmi"; import { MdLogout } from "react-icons/md"; -import classNames from "classnames"; import { formatAddress } from "@/utils/format"; -import { useWeb3Modal } from "@web3modal/wagmi/react"; +import ConnectButton from "@/components/ConnectButton"; -type Props = { - className?: string; -}; - -export default function Wallet({ className = "" }: Props) { +export default function Wallet() { const detailsRef = useRef(null); const { address, isConnected } = useAccount(); const { disconnect } = useDisconnect(); - const { open } = useWeb3Modal(); useEffect(() => { const handleClickOutside = (e: MouseEvent) => { @@ -59,15 +53,7 @@ export default function Wallet({ className = "" }: Props) { return (
    - +
    ); } diff --git a/bridge-ui/src/components/shortcut/Shortcut.tsx b/bridge-ui/src/components/shortcut/Shortcut.tsx deleted file mode 100644 index f6300d20..00000000 --- a/bridge-ui/src/components/shortcut/Shortcut.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import Image from "next/image"; -import React, { useRef, useState } from "react"; -import { Swiper, SwiperRef, SwiperSlide } from "swiper/react"; -import { motion } from "framer-motion"; - -import "swiper/css"; -import classNames from "classnames"; -import { Shortcut as ShortcutInterface } from "@/models/shortcut"; -import { useShortcuts } from "../bridge/BridgeLayout"; - -const poweredBy = [ - { - label: "Onthis.xyz", - url: "https://onthis.xyz/", - logo: "/images/logo/onthis.svg", - }, - { - label: "Across", - url: "https://across.to/", - logo: "/images/logo/across.svg", - }, -]; - -const Shortcut: React.FC = () => { - const [progress, setProgress] = useState(0); - const sliderRef = useRef(null); - - const shortcuts = useShortcuts(); - - const handleClickNext = () => { - sliderRef.current?.swiper.slideNext(); - }; - - const handleClickPrev = () => { - sliderRef.current?.swiper.slidePrev(); - }; - - return ( -
    -
    -

    Shortcuts

    - - How does it work? - -
    - - {shortcuts.length > 0 && ( - { - setProgress(swiper.progress); - }} - className="mt-6 size-full" - > - {shortcuts.map((shortcut, index) => ( - - - - ))} - - )} - -
    -
    - Powered by - {poweredBy.map((item, index) => ( - - {item.label} - {item.label} - - ))} -
    -
    - - -
    -
    - -

    - Note: *.onlinea.eth shortcuts are curated by Linea, but provided by Ecosystem Partners and Community. They are - not canonical solutions and they include additional fees collected only by involved partners. -

    -
    - ); -}; - -const ShortcutCard: React.FC<{ - item: ShortcutInterface; - className?: string; - index?: number; -}> = ({ item, className, index = 0 }) => { - const { title, description, logo, ens_name } = item; - const [buttonClicked, setButtonClicked] = useState(false); - - const onButtonClick = () => { - setButtonClicked(true); - window.navigator.clipboard.writeText(ens_name); - setTimeout(() => setButtonClicked(false), 3000); - }; - - return ( - -
    - {logo && {title}} -
    {description}
    - {ens_name && ( - - )} -
    -
    - ); -}; - -export default Shortcut; diff --git a/bridge-ui/src/components/terms/TermsModal.tsx b/bridge-ui/src/components/terms/TermsModal.tsx index 79cc1fd6..34f4a7b9 100644 --- a/bridge-ui/src/components/terms/TermsModal.tsx +++ b/bridge-ui/src/components/terms/TermsModal.tsx @@ -5,8 +5,10 @@ import { useConfigStore } from "@/stores/configStore"; export default function TermsModal() { const termsModalRef = useRef(null); - const { agreeToTerms, setAgreeToTerms } = useConfigStore((state) => ({ + + const { agreeToTerms, rehydrated, setAgreeToTerms } = useConfigStore((state) => ({ agreeToTerms: state.agreeToTerms, + rehydrated: state.rehydrated, setAgreeToTerms: state.setAgreeToTerms, })); @@ -24,14 +26,18 @@ export default function TermsModal() { }; useEffect(() => { - if (window && isFirstTime()) { + if (rehydrated && window && isFirstTime()) { setTimeout(() => { setOpen(true); setVideoEnabled(true); }, 1000); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [rehydrated]); + + if (!rehydrated) { + return null; + } return (
    + No bridge transactions found + + Bridge assets + +
    + ); +} diff --git a/bridge-ui/src/components/transactions/StatusText.tsx b/bridge-ui/src/components/transactions/StatusText.tsx new file mode 100644 index 00000000..651ffa33 --- /dev/null +++ b/bridge-ui/src/components/transactions/StatusText.tsx @@ -0,0 +1,25 @@ +import { OnChainMessageStatus } from "@consensys/linea-sdk"; +import { useMemo } from "react"; + +type StatusTextProps = { + status: OnChainMessageStatus; +}; + +const useFormattedStatus = (status: OnChainMessageStatus): JSX.Element => { + return useMemo(() => { + switch (status) { + case OnChainMessageStatus.CLAIMABLE: + return Ready to claim; + case OnChainMessageStatus.UNKNOWN: + return Pending; + case OnChainMessageStatus.CLAIMED: + return Completed; + default: + throw new Error(`Incorrect transaction status: ${status}`); + } + }, [status]); +}; + +export default function StatusText({ status }: StatusTextProps) { + return useFormattedStatus(status); +} diff --git a/bridge-ui/src/components/transactions/TransactionItem.tsx b/bridge-ui/src/components/transactions/TransactionItem.tsx new file mode 100644 index 00000000..ecd75a28 --- /dev/null +++ b/bridge-ui/src/components/transactions/TransactionItem.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useContext } from "react"; +import { formatEther } from "viem"; +import { ModalContext } from "@/contexts/modal.context"; +import StatusText from "./StatusText"; +import TransactionProgressBar from "./TransactionProgressBar"; +import TransactionDetailsModal from "./modals/TransactionDetailsModal"; +import { NETWORK_ID_TO_NAME } from "@/utils/constants"; +import { getChainNetworkLayerByChainId } from "@/utils/chainsUtil"; +import { TransactionHistory } from "@/models/history"; +import { MessageWithStatus } from "@/hooks"; + +export enum TransactionStatus { + READY_TO_CLAIM = "READY_TO_CLAIM", + PENDING = "PENDING", + COMPLETED = "COMPLETED", +} + +type TransactionItemProps = { + transaction: TransactionHistory; + message: MessageWithStatus; +}; + +export default function TransactionItem({ transaction, message }: TransactionItemProps) { + const { handleShow } = useContext(ModalContext); + + return ( +
    { + handleShow(); + }} + > +
    +
    +
    Status
    + +
    + +
    +
    From
    + {NETWORK_ID_TO_NAME[transaction.fromChain.id]} +
    +
    + +
    + +
    + +
    +
    +
    To
    + {NETWORK_ID_TO_NAME[transaction.toChain.id]} +
    + +
    +
    Amount
    + + {formatEther(transaction.amount)} {transaction.token.symbol} + +
    +
    + +
    + +
    +
    + ); +} diff --git a/bridge-ui/src/components/transactions/TransactionProgressBar.tsx b/bridge-ui/src/components/transactions/TransactionProgressBar.tsx new file mode 100644 index 00000000..2c38cac5 --- /dev/null +++ b/bridge-ui/src/components/transactions/TransactionProgressBar.tsx @@ -0,0 +1,137 @@ +import { useMemo } from "react"; +import { fromUnixTime } from "date-fns"; +import { OnChainMessageStatus } from "@consensys/linea-sdk"; +import { NetworkLayer } from "@/config"; + +type TransactionProgressBarProps = { + status: OnChainMessageStatus; + transactionTimestamp: bigint; + fromChain?: NetworkLayer; +}; + +type TimeUnit = "seconds" | "minutes" | "hours"; + +const L1_L2_ESTIMATED_TIME_IN_SECONDS = 20 * 60; // 20 minutes +const L2_L1_MIN_TIME_IN_SECONDS = 8 * 60 * 60; // 8 hours +const L2_L1_MAX_TIME_IN_SECONDS = 32 * 60 * 60; // 32 hours + +const getElapsedTime = (startTime: Date, currentTime: Date): number => { + return (currentTime.getTime() - startTime.getTime()) / 1000; +}; + +const getRemainingTime = (startTime: Date, currentTime: Date, unit: TimeUnit, fromChain: NetworkLayer): string => { + const totalTime = + fromChain === NetworkLayer.L1 + ? L1_L2_ESTIMATED_TIME_IN_SECONDS + : (L2_L1_MIN_TIME_IN_SECONDS + L2_L1_MAX_TIME_IN_SECONDS) / 2; + + const elapsedTime = getElapsedTime(startTime, currentTime); + const remainingTimeInSeconds = Math.max(totalTime - elapsedTime, 0); + + const hours = Math.floor(remainingTimeInSeconds / 3600); + const minutes = Math.floor((remainingTimeInSeconds % 3600) / 60); + const seconds = Math.floor(remainingTimeInSeconds % 60); + + switch (unit) { + case "seconds": + return `${seconds} secs`; + case "minutes": + return `${minutes} min`; + case "hours": + return `${hours} hrs`; + default: + throw new Error("Invalid time unit."); + } +}; + +const getCompletionPercentage = ( + startTime: Date, + currentTime: Date, + status: OnChainMessageStatus, + fromChain?: NetworkLayer, +): number => { + if (status === OnChainMessageStatus.CLAIMED) { + return 100; + } + + if (!fromChain) { + throw new Error("Invalid network layer."); + } + + const elapsedTime = getElapsedTime(startTime, currentTime); + + let totalTime: number; + + if (fromChain === NetworkLayer.L1) { + totalTime = L1_L2_ESTIMATED_TIME_IN_SECONDS; // Convert 20 minutes to seconds + } else if (fromChain === NetworkLayer.L2) { + totalTime = (L2_L1_MIN_TIME_IN_SECONDS + L2_L1_MAX_TIME_IN_SECONDS) / 2; + } else { + throw new Error("Invalid network layer."); + } + + // Calculate the completion percentage + const completionPercentage = (elapsedTime / totalTime) * 100; + + // Ensure the percentage does not exceed 100% + return Math.min(completionPercentage, 100); +}; + +const getProgressBarText = ( + startTime: Date, + currentTime: Date, + status: OnChainMessageStatus, + fromChain?: NetworkLayer, +): string => { + if (!fromChain) { + throw new Error("Invalid network layer."); + } + + if (status === OnChainMessageStatus.CLAIMABLE || status === OnChainMessageStatus.CLAIMED) { + return "Complete"; + } + + const unit = fromChain === NetworkLayer.L1 ? "minutes" : "hours"; + + return `Est time left ${getRemainingTime(startTime, currentTime, unit, fromChain)}`; +}; + +const useProgressBarColor = (status: OnChainMessageStatus): string => { + return useMemo(() => { + switch (status) { + case OnChainMessageStatus.CLAIMABLE: + return "progress-secondary"; + case OnChainMessageStatus.UNKNOWN: + return "progress-info"; + case OnChainMessageStatus.CLAIMED: + return "progress-success"; + default: + throw new Error(`Incorrect transaction status: ${status}`); + } + }, [status]); +}; + +export default function TransactionProgressBar({ + status, + transactionTimestamp, + fromChain, +}: TransactionProgressBarProps) { + const color = useProgressBarColor(status); + return ( + <> +
    + {[OnChainMessageStatus.CLAIMABLE, OnChainMessageStatus.UNKNOWN].includes(status) && ( + + )} + + {getProgressBarText(fromUnixTime(Number(transactionTimestamp)), new Date(), status, fromChain)} + +
    + + + ); +} diff --git a/bridge-ui/src/components/transactions/Transactions.tsx b/bridge-ui/src/components/transactions/Transactions.tsx new file mode 100644 index 00000000..eea2f65e --- /dev/null +++ b/bridge-ui/src/components/transactions/Transactions.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useEffect, useMemo } from "react"; +import { useBlockNumber } from "wagmi"; +import { toast } from "react-toastify"; +import TransactionItem from "./TransactionItem"; +import { TransactionHistory } from "@/models/history"; +import { formatDate, fromUnixTime } from "date-fns"; +import { NoTransactions } from "./NoTransaction"; +import useFetchHistory from "@/hooks/useFetchHistory"; + +const groupByDay = (transactions: TransactionHistory[]): Record => { + return transactions.reduce( + (acc, transaction) => { + const date = formatDate(fromUnixTime(Number(transaction.timestamp)), "yyyy-MM-dd"); + if (!acc[date]) { + acc[date] = []; + } + acc[date].push(transaction); + return acc; + }, + {} as Record, + ); +}; + +export function Transactions() { + const { data: blockNumber } = useBlockNumber({ + watch: true, + }); + + // Context + const { transactions, fetchHistory, isLoading, clearHistory } = useFetchHistory(); + + useEffect(() => { + if (blockNumber && blockNumber % 5n === 0n) { + fetchHistory(); + } + }, [blockNumber, fetchHistory]); + + const groupedTransactions = useMemo(() => groupByDay(transactions), [transactions]); + + if (isLoading && transactions.length === 0) { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); + } + + if (transactions.length === 0) { + return ; + } + + return ( +
    +
    + +
    + {Object.keys(groupedTransactions).map((date, groupIndex) => ( +
    + {formatDate(date, "PPP")} + {groupedTransactions[date].map((transaction, transactionIndex) => { + if (transaction.messages && transaction.messages.length > 0 && transaction.messages[0].status) { + const { messages, ...bridgingTransaction } = transaction; + return ( + + ); + } + })} +
    + ))} +
    + ); +} diff --git a/bridge-ui/src/components/transactions/index.tsx b/bridge-ui/src/components/transactions/index.tsx new file mode 100644 index 00000000..c073bf48 --- /dev/null +++ b/bridge-ui/src/components/transactions/index.tsx @@ -0,0 +1,2 @@ +export * from "./NoTransaction"; +export * from "./Transactions"; diff --git a/bridge-ui/src/components/transactions/modals/TransactionClaimButton.tsx b/bridge-ui/src/components/transactions/modals/TransactionClaimButton.tsx new file mode 100644 index 00000000..f516b85b --- /dev/null +++ b/bridge-ui/src/components/transactions/modals/TransactionClaimButton.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from "react"; +import classNames from "classnames"; +import { toast } from "react-toastify"; +import { useSwitchNetwork } from "@/hooks"; +import { Transaction } from "@/models"; +import { TransactionHistory } from "@/models/history"; +import { useWaitForTransactionReceipt } from "wagmi"; +import { useChainStore } from "@/stores/chainStore"; +import useTransactionManagement, { MessageWithStatus } from "@/hooks/useTransactionManagement"; + +interface Props { + message: MessageWithStatus; + transaction: TransactionHistory; +} + +export default function TransactionClaimButton({ message, transaction }: Props) { + const [waitingTransaction, setWaitingTransaction] = useState(); + const [isClaimingLoading, setIsClaimingLoading] = useState(false); + + // Context + const toChain = useChainStore((state) => state.toChain); + + // Hooks + const { switchChainById } = useSwitchNetwork(toChain?.id); + const { writeClaimMessage, isLoading: isTxLoading, transaction: claimTx } = useTransactionManagement(); + + // Wagmi + const { + isLoading: isWaitingLoading, + isSuccess: isWaitingSuccess, + isError: isWaitingError, + } = useWaitForTransactionReceipt({ + hash: waitingTransaction?.txHash, + chainId: waitingTransaction?.chainId, + }); + + const claimBusy = isClaimingLoading || isTxLoading || isWaitingLoading; + + useEffect(() => { + if (claimTx) { + setWaitingTransaction({ + txHash: claimTx.txHash, + chainId: transaction.toChain.id, + name: transaction.toChain.name, + }); + } + }, [claimTx, transaction.toChain.id, transaction.toChain.name]); + + useEffect(() => { + if (isWaitingSuccess) { + toast.success(`Funds claimed on ${transaction.toChain.name}.`); + setWaitingTransaction(undefined); + } + }, [isWaitingSuccess, transaction.toChain.name]); + + useEffect(() => { + if (isWaitingError) { + toast.error("Funds claiming failed."); + setWaitingTransaction(undefined); + } + }, [isWaitingError]); + + const onClaimMessage = async () => { + if (isClaimingLoading) { + return; + } + + try { + setIsClaimingLoading(true); + await switchChainById(transaction.toChain.id); + await writeClaimMessage(message, transaction); + } catch (error) { + toast.error("Failed to claim funds. Please try again."); + } finally { + setIsClaimingLoading(false); + } + }; + + return ( + + ); +} diff --git a/bridge-ui/src/components/transactions/modals/TransactionDetailsModal.tsx b/bridge-ui/src/components/transactions/modals/TransactionDetailsModal.tsx new file mode 100644 index 00000000..1ec33a32 --- /dev/null +++ b/bridge-ui/src/components/transactions/modals/TransactionDetailsModal.tsx @@ -0,0 +1,71 @@ +import Link from "next/link"; +import { OnChainMessageStatus } from "@consensys/linea-sdk"; +import { formatHex, formatTimestamp } from "@/utils/format"; +import { NETWORK_ID_TO_NAME } from "@/utils/constants"; +import { MessageWithStatus } from "@/hooks"; +import { TransactionHistory } from "@/models/history"; +import TransactionClaimButton from "./TransactionClaimButton"; + +type TransactionDetailsModalProps = { + transaction: TransactionHistory; + message: MessageWithStatus; +}; + +const BlockExplorerLink: React.FC<{ + transactionHash?: string; + blockExplorer?: string; +}> = ({ transactionHash, blockExplorer }) => { + if (!transactionHash || !blockExplorer) { + return N/A; + } + return ( + + {formatHex(transactionHash)} + + ); +}; + +const TransactionDetailsModal: React.FC = ({ transaction, message }) => { + return ( +
    +

    Transaction details

    +
    +
    + + {formatTimestamp(Number(transaction.timestamp), "h:mma d MMMM yyyy")} +
    + +
    + + +
    + +
    + + +
    +
    + + 5 ETH ~$15000 +
    +
    + {message.status === OnChainMessageStatus.CLAIMABLE && ( + + )} +
    + ); +}; + +export default TransactionDetailsModal; diff --git a/bridge-ui/src/components/widgets/SwitchNetwork.tsx b/bridge-ui/src/components/widgets/SwitchNetwork.tsx index a1b28896..6a787422 100644 --- a/bridge-ui/src/components/widgets/SwitchNetwork.tsx +++ b/bridge-ui/src/components/widgets/SwitchNetwork.tsx @@ -38,10 +38,8 @@ export default function SwitchNetwork() { if (!isConnected) return null; return ( -
    - -
    + ); } diff --git a/bridge-ui/src/contexts/modal.context.tsx b/bridge-ui/src/contexts/modal.context.tsx new file mode 100644 index 00000000..1019ef49 --- /dev/null +++ b/bridge-ui/src/contexts/modal.context.tsx @@ -0,0 +1,46 @@ +"use client"; + +import React, { createContext, ReactNode, useCallback, useRef, useState } from "react"; +import Modal from "@/components/layouts/Modal"; + +interface ModalContextType { + ref: React.MutableRefObject; + handleShow: (content: ReactNode, options?: ModalOptions) => void; + handleClose: () => void; + modalContent: ReactNode; + options: ModalOptions; +} + +export const ModalContext = createContext({} as ModalContextType); + +interface ModalProviderProps { + children: ReactNode; +} + +interface ModalOptions { + width?: string; +} + +export const ModalProvider: React.FC = ({ children }) => { + const ref = useRef(null); + const [modalContent, setModalContent] = useState(null); + const [options, setOptions] = useState({}); + + const handleShow = useCallback((content: ReactNode, options?: ModalOptions) => { + options && setOptions(options); + setModalContent(content); + ref.current?.showModal(); + }, []); + + const handleClose = useCallback(() => { + ref.current?.close(); + setModalContent(null); + }, []); + + return ( + + {children} + + + ); +}; diff --git a/bridge-ui/src/data/shortcuts.md b/bridge-ui/src/data/shortcuts.md deleted file mode 100644 index f974a0a9..00000000 --- a/bridge-ui/src/data/shortcuts.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -- title: 'Bridge and ReStake' - description: 'Bridge ETH from Mainnet to Linea.' - logo: '/images/logo/linea-logo.svg' - ens_name: 'bridge.onlinea.eth' - -- title: 'Bridge and ReStake' - description: 'Bridge ETH from Mainnet, keep 0.005 in ETH and swap the remaining for wstETH.' - logo: '/images/logo/lido.svg' - ens_name: 'lido.onlinea.eth' - -- title: 'Bridge and ReStake' - description: 'Bridge ETH from Mainnet, keep 0.005 in ETH and restake the remaining in ezETH.' - logo: '/images/logo/renzo.svg' - ens_name: 'renzo.onlinea.eth' - -# - title: 'Bridge and ReStake' -# description: 'Low gas fees and low latency with high throughput backed by the security of Ethereum.' -# logo: '/images/logo/renzo.svg' -# ens_name: 'onrenzo.linea.eth' ---- - -Each shortcut is a block with the following structure: - -``` -- title: ... - description: ... - logo: ... - ens_name: ... -``` - -Copy and paste the block and change the values to create a new shortcut. - -> **_NOTE:_** If you use internal image for `logo`, please make sure to add the image to this repository at `bridge-ui/public/images/...` diff --git a/bridge-ui/src/hooks/index.ts b/bridge-ui/src/hooks/index.ts index 07af4ce3..ca623361 100644 --- a/bridge-ui/src/hooks/index.ts +++ b/bridge-ui/src/hooks/index.ts @@ -4,7 +4,10 @@ export { default as useBridge } from "./useBridge"; export { default as useExecutionFee } from "./useExecutionFee"; export { default as useFetchAnchoringEvents } from "./useFetchAnchoringEvents"; export { default as useFetchBridgeTransactions } from "./useFetchBridgeTransactions"; -export { default as useMessageService } from "./useMessageService"; +export { default as useMinimumFee } from "./useMinimumFee"; +export { default as useTransactionManagement } from "./useTransactionManagement"; +export { default as useLineaSDK } from "./useLineaSDK"; +export { default as useMessageStatus } from "./useMessageStatus"; export { default as useSwitchNetwork } from "./useSwitchNetwork"; -export type { MessageWithStatus } from "./useMessageService"; +export type { MessageWithStatus } from "./useTransactionManagement"; diff --git a/bridge-ui/src/hooks/useBridge.ts b/bridge-ui/src/hooks/useBridge.ts index 7a21e3da..c1208515 100644 --- a/bridge-ui/src/hooks/useBridge.ts +++ b/bridge-ui/src/hooks/useBridge.ts @@ -7,13 +7,13 @@ import log from "loglevel"; import USDCBridge from "@/abis/USDCBridge.json"; import TokenBridge from "@/abis/TokenBridge.json"; import MessageService from "@/abis/MessageService.json"; -import useMessageService from "./useMessageService"; import { TokenInfo, TokenType, config } from "@/config/config"; import { BridgeError, BridgeErrors, Transaction } from "@/models"; import { getChainNetworkLayer } from "@/utils/chainsUtil"; import { FieldErrors, FieldValues } from "react-hook-form"; import { wagmiConfig } from "@/config"; import { useChainStore } from "@/stores/chainStore"; +import useMinimumFee from "./useMinimumFee"; type UseBridge = { hash: Address | undefined; @@ -45,7 +45,7 @@ const useBridge = (): UseBridge => { toChain: state.toChain, })); - const { minimumFee } = useMessageService(); + const { minimumFee } = useMinimumFee(); const { address, isConnected } = useAccount(); const queryClient = useQueryClient(); diff --git a/bridge-ui/src/hooks/useFetchBridgeTransactions.ts b/bridge-ui/src/hooks/useFetchBridgeTransactions.ts index 63c424fc..dc2e8c22 100644 --- a/bridge-ui/src/hooks/useFetchBridgeTransactions.ts +++ b/bridge-ui/src/hooks/useFetchBridgeTransactions.ts @@ -17,18 +17,17 @@ import useERC20Storage from "./useERC20Storage"; import { BlockRange, TransactionHistory } from "@/models/history"; import useFetchAnchoringEvents from "./useFetchAnchoringEvents"; import { OnChainMessageStatus } from "@consensys/linea-sdk"; -import useMessageService from "./useMessageService"; import useBridge from "./useBridge"; import { getChainNetworkLayer } from "@/utils/chainsUtil"; import { useTokenStore } from "@/stores/tokenStore"; +import useMessageStatus from "./useMessageStatus"; const useFetchBridgeTransactions = () => { // Wagmi const { address } = useAccount(); - const tokensConfig = useTokenStore((state) => state.tokensConfig); const { fetchAnchoringMessageHashes } = useFetchAnchoringEvents(); - const { getMessagesStatusesByTransactionHash } = useMessageService(); + const { getMessageStatuses } = useMessageStatus(); const { fetchBridgedToken, fillMissingTokenAddress } = useBridge(); const { updateOrInsertUserTokenList } = useERC20Storage(); @@ -118,7 +117,8 @@ const useFetchBridgeTransactions = () => { updateOrInsertUserTokenList(transaction.token, networkType); } - const newMessages = await getMessagesStatusesByTransactionHash(txHash, fromLayer); + const newMessages = await getMessageStatuses(txHash, fromLayer); + const updatedTransaction = { ...transaction, token: { diff --git a/bridge-ui/src/hooks/useFetchHistory.ts b/bridge-ui/src/hooks/useFetchHistory.ts index 59b7534e..7db1efa5 100644 --- a/bridge-ui/src/hooks/useFetchHistory.ts +++ b/bridge-ui/src/hooks/useFetchHistory.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from "react"; +import { useCallback } from "react"; import { useAccount } from "wagmi"; import log from "loglevel"; import { useChainStore } from "@/stores/chainStore"; @@ -11,9 +11,6 @@ import useFetchBridgeTransactions from "./useFetchBridgeTransactions"; const DEFAULT_FIRST_BLOCK = BigInt(1000); const useFetchHistory = () => { - // Prevent double fetching - const [isFetching, setIsFetching] = useState(false); - // Wagmi const { address } = useAccount(); @@ -43,11 +40,11 @@ const useFetchHistory = () => { if (!l1Chain || !l2Chain || !address) { return; } + // Prevent multiple call - if (isFetching) return; + if (isLoading) return; try { - setIsFetching(true); setIsLoading(true); // ToBlock: get last onchain block @@ -75,11 +72,10 @@ const useFetchHistory = () => { } catch (error) { log.error(error); } finally { - setIsFetching(false); setIsLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [address, currentNetworkType, isFetching, l1Chain, l2Chain]); + }, [address, currentNetworkType, l1Chain, l2Chain]); const clearHistory = useCallback(() => { // Clear local storage diff --git a/bridge-ui/src/hooks/useInitialiseChain.ts b/bridge-ui/src/hooks/useInitialiseChain.ts index 91c5ed34..28eb530b 100644 --- a/bridge-ui/src/hooks/useInitialiseChain.ts +++ b/bridge-ui/src/hooks/useInitialiseChain.ts @@ -50,7 +50,7 @@ const useInitialiseChain = () => { })); useEffect(() => { - watchAccount(wagmiConfig, { + const unwatch = watchAccount(wagmiConfig, { onChange(account) { let networkType = NetworkType.UNKNOWN; let networkLayer = NetworkLayer.UNKNOWN; @@ -118,6 +118,10 @@ const useInitialiseChain = () => { setToChain(toChain); }, }); + + return () => { + unwatch(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [token, token?.type]); diff --git a/bridge-ui/src/hooks/useInitialiseToken.ts b/bridge-ui/src/hooks/useInitialiseToken.ts index 77eeb35d..08ae229d 100644 --- a/bridge-ui/src/hooks/useInitialiseToken.ts +++ b/bridge-ui/src/hooks/useInitialiseToken.ts @@ -1,36 +1,13 @@ +import { useEffect } from "react"; import { NetworkTokens, TokenInfo, TokenType } from "@/config"; import { Token } from "@/models/token"; +import { getTokens, USDC_TYPE } from "@/services"; import { defaultTokensConfig, useTokenStore } from "@/stores/tokenStore"; -import log from "loglevel"; -import { useEffect } from "react"; enum NetworkTypes { MAINNET = "MAINNET", SEPOLIA = "SEPOLIA", } -const CANONICAL_BRIDGED_TYPE = "canonical-bridge"; -const USDC_TYPE = "USDC"; - -async function getTokens(networkTypes: NetworkTypes): Promise { - try { - // Fetch the JSON data from the URL. - let url = process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST ? (process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST as string) : ""; - if (networkTypes === NetworkTypes.SEPOLIA) { - url = process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST ? (process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST as string) : ""; - } - - const response = await fetch(url); - const data = await response.json(); - const tokens = data.tokens; - const bridgedTokens = tokens.filter( - (token: Token) => token.tokenType.includes(CANONICAL_BRIDGED_TYPE) || token.symbol === USDC_TYPE, - ); - return bridgedTokens; - } catch (error) { - log.error("Error getTokens", { error }); - return []; - } -} export async function getConfig(): Promise { const mainnetTokens = await getTokens(NetworkTypes.MAINNET); diff --git a/bridge-ui/src/hooks/useLineaSDK.ts b/bridge-ui/src/hooks/useLineaSDK.ts new file mode 100644 index 00000000..72950827 --- /dev/null +++ b/bridge-ui/src/hooks/useLineaSDK.ts @@ -0,0 +1,52 @@ +import { useMemo } from "react"; +import { LineaSDK, Network } from "@consensys/linea-sdk"; +import { L1MessageServiceContract, L2MessageServiceContract } from "@consensys/linea-sdk/dist/lib/contracts"; +import { NetworkType } from "@/config"; +import { useChainStore } from "@/stores/chainStore"; + +interface LineaSDKContracts { + L1: L1MessageServiceContract; + L2: L2MessageServiceContract; +} + +const useLineaSDK = () => { + const networkType = useChainStore((state) => state.networkType); + + const { lineaSDK, lineaSDKContracts } = useMemo(() => { + const infuraKey = process.env.NEXT_PUBLIC_INFURA_ID; + if (!infuraKey) return { lineaSDK: null, lineaSDKContracts: null }; + + let l1RpcUrl; + let l2RpcUrl; + switch (networkType) { + case NetworkType.MAINNET: + l1RpcUrl = `https://mainnet.infura.io/v3/${infuraKey}`; + l2RpcUrl = `https://linea-mainnet.infura.io/v3/${infuraKey}`; + break; + case NetworkType.SEPOLIA: + l1RpcUrl = `https://sepolia.infura.io/v3/${infuraKey}`; + l2RpcUrl = `https://linea-sepolia.infura.io/v3/${infuraKey}`; + break; + default: + return { lineaSDK: null, lineaSDKContracts: null }; + } + + const sdk = new LineaSDK({ + l1RpcUrl, + l2RpcUrl, + network: `linea-${networkType.toLowerCase()}` as Network, + mode: "read-only", + }); + + const newLineaSDKContracts: LineaSDKContracts = { + L1: sdk.getL1Contract(), + L2: sdk.getL2Contract(), + }; + + return { lineaSDK: sdk, lineaSDKContracts: newLineaSDKContracts }; + }, [networkType]); + + return { lineaSDK, lineaSDKContracts }; +}; + +export default useLineaSDK; diff --git a/bridge-ui/src/hooks/useMessageService.ts b/bridge-ui/src/hooks/useMessageService.ts deleted file mode 100644 index d3d8c5bc..00000000 --- a/bridge-ui/src/hooks/useMessageService.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { useState, useCallback, useMemo } from "react"; -import { readContract, simulateContract, writeContract } from "@wagmi/core"; -import { useAccount } from "wagmi"; -import { zeroAddress } from "viem"; -import log from "loglevel"; -import { LineaSDK, OnChainMessageStatus } from "@consensys/linea-sdk"; -import { L1MessageServiceContract, L2MessageServiceContract } from "@consensys/linea-sdk/dist/lib/contracts"; -import MessageService from "@/abis/MessageService.json"; -import { getChainNetworkLayer, getChainNetworkType } from "@/utils/chainsUtil"; -import { NetworkLayer, NetworkType, config, wagmiConfig } from "@/config"; -import { Transaction } from "@/models"; -import { TransactionHistory } from "@/models/history"; -import { Proof } from "@consensys/linea-sdk/dist/lib/sdk/merkleTree/types"; -import { useChainStore } from "@/stores/chainStore"; - -interface LineaSDKContracts { - L1: L1MessageServiceContract; - L2: L2MessageServiceContract; -} - -export interface MessageWithStatus { - status: OnChainMessageStatus; - messageSender: string; - destination: string; - fee: string; - value: string; - messageNonce: string; - calldata: string; - messageHash: string; - proof: Proof | undefined; -} - -interface ClaimMessageWithProofParams { - proof: string[]; - messageNumber: string; - leafIndex: number; - from: string; - to: string; - fee: string; - value: string; - feeRecipient: string; - merkleRoot: string; - data: string; -} - -const useMessageService = () => { - const [transaction, setTransaction] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const [minimumFee, setMinimumFee] = useState(BigInt(0)); - const [lineaSDK, setLineaSDK] = useState(undefined); - const [lineaSDKContracts, setLineaSDKContracts] = useState(undefined); - - const { messageServiceAddress, fromChain, networkType } = useChainStore((state) => ({ - messageServiceAddress: state.messageServiceAddress, - fromChain: state.fromChain, - networkType: state.networkType, - })); - const { address } = useAccount(); - - useMemo(() => { - let _lineaSDK; - const infuraKey = process.env.NEXT_PUBLIC_INFURA_ID; - switch (networkType) { - case NetworkType.MAINNET: - _lineaSDK = new LineaSDK({ - l1RpcUrl: `https://mainnet.infura.io/v3/${infuraKey}`, - l2RpcUrl: `https://linea-mainnet.infura.io/v3/${infuraKey}`, - network: "linea-mainnet", - mode: "read-only", - }); - - break; - case NetworkType.SEPOLIA: - _lineaSDK = new LineaSDK({ - l1RpcUrl: `https://sepolia.infura.io/v3/${infuraKey}`, - l2RpcUrl: `https://linea-sepolia.infura.io/v3/${infuraKey}`, - network: "linea-sepolia", - mode: "read-only", - }); - break; - } - if (!_lineaSDK) { - return; - } - const newLineaSDKContracts: LineaSDKContracts = { - L1: _lineaSDK.getL1Contract(), - L2: _lineaSDK.getL2Contract(), - }; - setLineaSDKContracts(newLineaSDKContracts); - setLineaSDK(_lineaSDK); - }, [networkType]); - - useMemo(() => { - const readMinimumFee = async () => { - setError(null); - setIsLoading(true); - - if (!messageServiceAddress) { - return; - } - - try { - let fees = BigInt(0); - if (fromChain && getChainNetworkLayer(fromChain) === NetworkLayer.L2) { - //Get the minimum to send along the message for L2 - fees = (await readContract(wagmiConfig, { - address: messageServiceAddress, - abi: MessageService.abi, - functionName: "minimumFeeInWei", - chainId: fromChain.id, - })) as bigint; - } - setMinimumFee(fees); - } catch (error) { - setError(error as Error); - } - - setIsLoading(false); - }; - - readMinimumFee(); - }, [messageServiceAddress, fromChain]); - - const getMessagesByTransactionHash = useCallback( - async (transactionHash: string, networkLayer: NetworkLayer) => { - if (!lineaSDKContracts || networkLayer === NetworkLayer.UNKNOWN) { - return; - } - return await lineaSDKContracts[networkLayer]?.getMessagesByTransactionHash(transactionHash); - }, - [lineaSDKContracts], - ); - - const getMessagesStatusesByTransactionHash = useCallback( - async (transactionHash: string, networkLayer: NetworkLayer) => { - if (!lineaSDKContracts || networkLayer === NetworkLayer.UNKNOWN) { - return; - } - const messages = await getMessagesByTransactionHash(transactionHash, networkLayer); - - const messagesWithStatuses: Array = []; - if (messages && messages.length > 0) { - const otherLayer = networkLayer === NetworkLayer.L1 ? NetworkLayer.L2 : NetworkLayer.L1; - - const promises = messages.map(async (message) => { - const l1ClaimingService = lineaSDK?.getL1ClaimingService( - config.networks[networkType].L1.messageServiceAddress, - config.networks[networkType].L2.messageServiceAddress, - ); - let status; - // For messages to claim on L1 we check if we need to claim with the new claiming method - // which requires the proof linked to this message - let proof; - - if (otherLayer === NetworkLayer.L1) { - // Message from L2 to L1 - status = (await l1ClaimingService?.getMessageStatus(message.messageHash)) || OnChainMessageStatus.UNKNOWN; - if ( - status === OnChainMessageStatus.CLAIMABLE && - (await l1ClaimingService?.isClaimingNeedingProof(message.messageHash)) - ) { - try { - proof = await l1ClaimingService?.getMessageProof(message.messageHash); - } catch (ex) { - // We ignore the error, the proof will stay undefined, we assume - // it's a message from the old message service - } - } - } else { - // Message from L1 to L2 - status = await lineaSDKContracts.L2.getMessageStatus(message.messageHash); - } - - // Convert the BigNumbers to string for serialization issue with the storage - const messageWithStatus: MessageWithStatus = { - calldata: message.calldata, - destination: message.destination, - fee: message.fee.toString(), - messageHash: message.messageHash, - messageNonce: message.messageNonce.toString(), - messageSender: message.messageSender, - status: status, - value: message.value.toString(), - proof, - }; - messagesWithStatuses.push(messageWithStatus); - }); - await Promise.all(promises); - } - - return messagesWithStatuses; - }, - [lineaSDKContracts, getMessagesByTransactionHash, lineaSDK, networkType], - ); - - const writeClaimMessage = useCallback( - async (message: MessageWithStatus, tx: TransactionHistory) => { - setError(null); - setIsLoading(true); - - // Get the right message Service address depending on the transaction - const txNetworkLayer = getChainNetworkLayer(tx.toChain); - const txNetworkType = getChainNetworkType(tx.toChain); - - if (address && txNetworkLayer && txNetworkType) { - try { - const { messageSender, destination, calldata, fee, messageNonce, value, proof } = message; - - const messageServiceAddress = config.networks[txNetworkType][txNetworkLayer].messageServiceAddress; - if (messageServiceAddress === null) { - return; - } - let writeConfig; - if (!proof) { - // Claiming using old message service - writeConfig = await simulateContract(wagmiConfig, { - address: messageServiceAddress, - abi: MessageService.abi, - functionName: "claimMessage", - args: [messageSender, destination, fee, value, zeroAddress, calldata, messageNonce], - chainId: tx.toChain.id, - }); - } else { - // Claiming on L1 with new message service - const params: ClaimMessageWithProofParams = { - data: calldata, - fee, - feeRecipient: zeroAddress, - from: messageSender, - to: destination, - leafIndex: proof.leafIndex, - merkleRoot: proof.root, - messageNumber: messageNonce, - proof: proof.proof, - value, - }; - writeConfig = await simulateContract(wagmiConfig, { - address: messageServiceAddress, - abi: MessageService.abi, - functionName: "claimMessageWithProof", - args: [params], - chainId: tx.toChain.id, - }); - } - - const hash = await writeContract(wagmiConfig, writeConfig.request); - - setTransaction({ - txHash: hash, - chainId: tx.fromChain.id, - name: tx.fromChain.name, - }); - } catch (error) { - log.error(error); - setError(error as Error); - setTransaction(null); - } - } - - setIsLoading(false); - }, - [address], - ); - - return { - isLoading, - isError: error !== null, - error, - minimumFee, - transaction, - getMessagesStatusesByTransactionHash, - writeClaimMessage, - }; -}; - -export default useMessageService; diff --git a/bridge-ui/src/hooks/useMessageStatus.ts b/bridge-ui/src/hooks/useMessageStatus.ts new file mode 100644 index 00000000..28981b9a --- /dev/null +++ b/bridge-ui/src/hooks/useMessageStatus.ts @@ -0,0 +1,102 @@ +import { useCallback } from "react"; +import { OnChainMessageStatus } from "@consensys/linea-sdk"; +import useLineaSDK from "./useLineaSDK"; +import { config, NetworkLayer } from "@/config"; +import { MessageWithStatus } from "./useTransactionManagement"; +import { useChainStore } from "@/stores/chainStore"; + +const useMessageStatus = () => { + const { lineaSDK, lineaSDKContracts } = useLineaSDK(); + + const networkType = useChainStore((state) => state.networkType); + + const getMessagesByTransactionHash = useCallback( + async (transactionHash: string, networkLayer: NetworkLayer) => { + if (!lineaSDKContracts || networkLayer === NetworkLayer.UNKNOWN) { + return; + } + return await lineaSDKContracts[networkLayer]?.getMessagesByTransactionHash(transactionHash); + }, + [lineaSDKContracts], + ); + + const getMessageStatuses = useCallback( + async (transactionHash: string, networkLayer: NetworkLayer) => { + if (!lineaSDKContracts || networkLayer === NetworkLayer.UNKNOWN) { + return; + } + + const messages = await getMessagesByTransactionHash(transactionHash, networkLayer); + + const messagesWithStatuses: Array = []; + if (messages && messages.length > 0) { + const otherLayer = networkLayer === NetworkLayer.L1 ? NetworkLayer.L2 : NetworkLayer.L1; + + const promises = messages.map(async (message) => { + const l1ClaimingService = lineaSDK?.getL1ClaimingService( + config.networks[networkType].L1.messageServiceAddress, + config.networks[networkType].L2.messageServiceAddress, + ); + let status: OnChainMessageStatus; + let claimingTransactionHash; + // For messages to claim on L1 we check if we need to claim with the new claiming method + // which requires the proof linked to this message + let proof; + + if (otherLayer === NetworkLayer.L1) { + // Message from L2 to L1 + status = (await l1ClaimingService?.getMessageStatus(message.messageHash)) || OnChainMessageStatus.UNKNOWN; + + if ( + status === OnChainMessageStatus.CLAIMABLE && + (await l1ClaimingService?.isClaimingNeedingProof(message.messageHash)) + ) { + try { + proof = await l1ClaimingService?.getMessageProof(message.messageHash); + } catch (ex) { + // We ignore the error, the proof will stay undefined, we assume + // it's a message from the old message service + } + } + if (status === OnChainMessageStatus.CLAIMED) { + const [messageClaimedEvent] = await lineaSDKContracts.L1.getEvents( + lineaSDKContracts.L1.contract.filters.MessageClaimed(message.messageHash), + ); + claimingTransactionHash = messageClaimedEvent ? messageClaimedEvent.transactionHash : undefined; + } + } else { + // Message from L1 to L2 + status = await lineaSDKContracts.L2.getMessageStatus(message.messageHash); + const [messageClaimedEvent] = await lineaSDKContracts.L2.getEvents( + lineaSDKContracts.L2.contract.filters.MessageClaimed(message.messageHash), + ); + claimingTransactionHash = messageClaimedEvent ? messageClaimedEvent.transactionHash : undefined; + } + + // Convert the BigNumbers to string for serialization issue with the storage + const messageWithStatus: MessageWithStatus = { + calldata: message.calldata, + destination: message.destination, + fee: message.fee.toString(), + messageHash: message.messageHash, + messageNonce: message.messageNonce.toString(), + messageSender: message.messageSender, + status: status, + value: message.value.toString(), + proof, + claimingTransactionHash, + }; + messagesWithStatuses.push(messageWithStatus); + }); + await Promise.all(promises); + } + + return messagesWithStatuses; + }, + [lineaSDKContracts, getMessagesByTransactionHash, lineaSDK, networkType], + ); + + return { getMessageStatuses }; +}; + +export default useMessageStatus; diff --git a/bridge-ui/src/hooks/useMinimumFee.ts b/bridge-ui/src/hooks/useMinimumFee.ts new file mode 100644 index 00000000..a46ef5a9 --- /dev/null +++ b/bridge-ui/src/hooks/useMinimumFee.ts @@ -0,0 +1,47 @@ +import { useState, useEffect, useCallback } from "react"; +import { readContract } from "@wagmi/core"; +import { useChainStore } from "@/stores/chainStore"; +import { NetworkLayer, wagmiConfig } from "@/config"; +import MessageService from "@/abis/MessageService.json"; +import { getChainNetworkLayer } from "@/utils/chainsUtil"; + +const useMinimumFee = () => { + const [minimumFee, setMinimumFee] = useState(BigInt(0)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [error, setError] = useState(null); + + const { messageServiceAddress, fromChain } = useChainStore((state) => ({ + messageServiceAddress: state.messageServiceAddress, + fromChain: state.fromChain, + })); + + const fetchMinimumFee = useCallback(async () => { + if (!messageServiceAddress) { + return; + } + + try { + let fees = BigInt(0); + if (fromChain && getChainNetworkLayer(fromChain) === NetworkLayer.L2) { + fees = (await readContract(wagmiConfig, { + address: messageServiceAddress, + abi: MessageService.abi, + functionName: "minimumFeeInWei", + chainId: fromChain.id, + })) as bigint; + } + setMinimumFee(fees); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + setError(err); + } + }, [messageServiceAddress, fromChain]); + + useEffect(() => { + fetchMinimumFee(); + }, [fetchMinimumFee]); + + return { minimumFee, error }; +}; + +export default useMinimumFee; diff --git a/bridge-ui/src/hooks/useTransactionManagement.ts b/bridge-ui/src/hooks/useTransactionManagement.ts new file mode 100644 index 00000000..b0e2c43b --- /dev/null +++ b/bridge-ui/src/hooks/useTransactionManagement.ts @@ -0,0 +1,118 @@ +import { useState, useCallback } from "react"; +import log from "loglevel"; +import { simulateContract, writeContract } from "@wagmi/core"; +import { zeroAddress } from "viem"; +import MessageService from "@/abis/MessageService.json"; +import { config, wagmiConfig } from "@/config"; +import { OnChainMessageStatus } from "@consensys/linea-sdk"; +import { Proof } from "@consensys/linea-sdk/dist/lib/sdk/merkleTree/types"; +import { TransactionHistory } from "@/models/history"; +import { getChainNetworkLayer, getChainNetworkType } from "@/utils/chainsUtil"; +import { useAccount } from "wagmi"; +import { Transaction } from "@/models"; + +export interface MessageWithStatus { + status: OnChainMessageStatus; + messageSender: string; + destination: string; + fee: string; + value: string; + messageNonce: string; + calldata: string; + messageHash: string; + proof: Proof | undefined; + claimingTransactionHash?: string; +} + +interface ClaimMessageWithProofParams { + proof: string[]; + messageNumber: string; + leafIndex: number; + from: string; + to: string; + fee: string; + value: string; + feeRecipient: string; + merkleRoot: string; + data: string; +} + +const useTransactionManagement = () => { + const { address } = useAccount(); + const [transaction, setTransaction] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const writeClaimMessage = useCallback( + async (message: MessageWithStatus, tx: TransactionHistory) => { + setError(null); + setIsLoading(true); + + // Get the right message Service address depending on the transaction + const txNetworkLayer = getChainNetworkLayer(tx.toChain); + const txNetworkType = getChainNetworkType(tx.toChain); + + if (address && txNetworkLayer && txNetworkType) { + try { + const { messageSender, destination, calldata, fee, messageNonce, value, proof } = message; + + const messageServiceAddress = config.networks[txNetworkType][txNetworkLayer].messageServiceAddress; + if (messageServiceAddress === null) { + return; + } + let writeConfig; + if (!proof) { + // Claiming using old message service + writeConfig = await simulateContract(wagmiConfig, { + address: messageServiceAddress, + abi: MessageService.abi, + functionName: "claimMessage", + args: [messageSender, destination, fee, value, zeroAddress, calldata, messageNonce], + chainId: tx.toChain.id, + }); + } else { + // Claiming on L1 with new message service + const params: ClaimMessageWithProofParams = { + data: calldata, + fee, + feeRecipient: zeroAddress, + from: messageSender, + to: destination, + leafIndex: proof.leafIndex, + merkleRoot: proof.root, + messageNumber: messageNonce, + proof: proof.proof, + value, + }; + writeConfig = await simulateContract(wagmiConfig, { + address: messageServiceAddress, + abi: MessageService.abi, + functionName: "claimMessageWithProof", + args: [params], + chainId: tx.toChain.id, + }); + } + + const hash = await writeContract(wagmiConfig, writeConfig.request); + + setTransaction({ + txHash: hash, + chainId: tx.fromChain.id, + name: tx.fromChain.name, + }); + } catch (error) { + log.error(error); + setError(error as Error); + setTransaction(null); + } + } + + setIsLoading(false); + }, + [address], + ); + + return { transaction, isLoading, isError: error !== null, error, writeClaimMessage }; +}; + +export default useTransactionManagement; diff --git a/bridge-ui/src/models/shortcut.ts b/bridge-ui/src/models/shortcut.ts deleted file mode 100644 index ef8f41e7..00000000 --- a/bridge-ui/src/models/shortcut.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Shortcut { - title: string; - description?: string; - logo: string; - ens_name: string; -} diff --git a/bridge-ui/src/services/fetchERC20Image.ts b/bridge-ui/src/services/fetchERC20Image.ts deleted file mode 100644 index ba995e19..00000000 --- a/bridge-ui/src/services/fetchERC20Image.ts +++ /dev/null @@ -1,48 +0,0 @@ -import axios, { AxiosResponse } from "axios"; -import log from "loglevel"; - -interface CoinGeckoToken { - id: string; - symbol: string; - name: string; -} - -interface CoinGeckoTokenDetail { - image: { - small: string; - }; -} - -async function fetchERC20Image(name: string) { - try { - if (!name) { - throw new Error("Name is required"); - } - - const coinsResponse: AxiosResponse = await axios.get( - "https://api.coingecko.com/api/v3/coins/list", - ); - const coin = coinsResponse.data.find((coin: CoinGeckoToken) => coin.name === name); - - if (!coin) { - throw new Error("Coin not found"); - } - - const coinId = coin.id; - const coinDataResponse: AxiosResponse = await axios.get( - `https://api.coingecko.com/api/v3/coins/${coinId}`, - ); - - if (!coinDataResponse.data.image.small) { - throw new Error("Image not found"); - } - - const image = coinDataResponse.data.image.small; - return image.split("?")[0]; - } catch (error) { - log.warn(error); - return "/images/logo/noTokenLogo.svg"; - } -} - -export default fetchERC20Image; diff --git a/bridge-ui/src/services/fetchTokenInfo.ts b/bridge-ui/src/services/fetchTokenInfo.ts deleted file mode 100644 index cff8a4ea..00000000 --- a/bridge-ui/src/services/fetchTokenInfo.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Address } from "viem"; -import { GetTokenReturnType, getToken } from "@wagmi/core"; -import { sepolia, linea, mainnet, lineaSepolia, Chain } from "viem/chains"; -import log from "loglevel"; -import fetchERC20Image from "@/services/fetchERC20Image"; -import { NetworkType, TokenInfo, TokenType, wagmiConfig } from "@/config"; - -const fetchTokenInfo = async ( - tokenAddress: Address, - networkType: NetworkType, - fromChain?: Chain, -): Promise => { - let erc20: GetTokenReturnType | undefined; - let chainFound; - - if (!chainFound) { - const chains: Chain[] = networkType === NetworkType.SEPOLIA ? [lineaSepolia, sepolia] : [linea, mainnet]; - - // Put the fromChain arg at the begining to take it as priority - if (fromChain) chains.unshift(fromChain); - - for (const chain of chains) { - try { - erc20 = await getToken(wagmiConfig, { - address: tokenAddress, - chainId: chain.id, - }); - if (erc20.name) { - // Found the token if no errors with fetchToken - chainFound = chain; - break; - } - } catch (err) { - continue; - } - } - } - - if (!erc20 || !chainFound || !erc20.name) { - return; - } - - const L1Token = chainFound.id === mainnet.id || chainFound.id === sepolia.id; - - // Fetch image - const name = erc20.name; - const image = await fetchERC20Image(name); - - try { - return { - name, - symbol: erc20.symbol!, - decimals: erc20.decimals, - L1: L1Token ? tokenAddress : null, - L2: !L1Token ? tokenAddress : null, - image, - type: TokenType.ERC20, - UNKNOWN: null, - isDefault: false, - }; - } catch (err) { - log.error(err); - return; - } -}; - -export default fetchTokenInfo; diff --git a/bridge-ui/src/services/index.ts b/bridge-ui/src/services/index.ts new file mode 100644 index 00000000..c185890e --- /dev/null +++ b/bridge-ui/src/services/index.ts @@ -0,0 +1 @@ +export { fetchERC20Image, fetchTokenInfo, getTokens, USDC_TYPE, CANONICAL_BRIDGED_TYPE } from "./tokenService"; diff --git a/bridge-ui/src/services/tokenService.ts b/bridge-ui/src/services/tokenService.ts new file mode 100644 index 00000000..a2418a0b --- /dev/null +++ b/bridge-ui/src/services/tokenService.ts @@ -0,0 +1,139 @@ +import axios, { AxiosResponse } from "axios"; +import log from "loglevel"; +import { Address } from "viem"; +import { GetTokenReturnType, getToken } from "@wagmi/core"; +import { sepolia, linea, mainnet, lineaSepolia, Chain } from "viem/chains"; +import { NetworkType, TokenInfo, TokenType, wagmiConfig } from "@/config"; +import { Token } from "@/models/token"; + +interface CoinGeckoToken { + id: string; + symbol: string; + name: string; +} + +interface CoinGeckoTokenDetail { + image: { + small: string; + }; +} + +enum NetworkTypes { + MAINNET = "MAINNET", + SEPOLIA = "SEPOLIA", +} + +export const CANONICAL_BRIDGED_TYPE = "canonical-bridge"; +export const USDC_TYPE = "USDC"; + +export async function fetchERC20Image(name: string) { + try { + if (!name) { + throw new Error("Name is required"); + } + + const coinsResponse: AxiosResponse = await axios.get( + "https://api.coingecko.com/api/v3/coins/list", + ); + const coin = coinsResponse.data.find((coin: CoinGeckoToken) => coin.name === name); + + if (!coin) { + throw new Error("Coin not found"); + } + + const coinId = coin.id; + const coinDataResponse: AxiosResponse = await axios.get( + `https://api.coingecko.com/api/v3/coins/${coinId}`, + ); + + if (!coinDataResponse.data.image.small) { + throw new Error("Image not found"); + } + + const image = coinDataResponse.data.image.small; + return image.split("?")[0]; + } catch (error) { + log.warn(error); + return "/images/logo/noTokenLogo.svg"; + } +} + +export async function fetchTokenInfo( + tokenAddress: Address, + networkType: NetworkType, + fromChain?: Chain, +): Promise { + let erc20: GetTokenReturnType | undefined; + let chainFound; + + if (!chainFound) { + const chains: Chain[] = networkType === NetworkType.SEPOLIA ? [lineaSepolia, sepolia] : [linea, mainnet]; + + // Put the fromChain arg at the begining to take it as priority + if (fromChain) chains.unshift(fromChain); + + for (const chain of chains) { + try { + erc20 = await getToken(wagmiConfig, { + address: tokenAddress, + chainId: chain.id, + }); + if (erc20.name) { + // Found the token if no errors with fetchToken + chainFound = chain; + break; + } + } catch (err) { + continue; + } + } + } + + if (!erc20 || !chainFound || !erc20.name) { + return; + } + + const L1Token = chainFound.id === mainnet.id || chainFound.id === sepolia.id; + + // Fetch image + const name = erc20.name; + const image = await fetchERC20Image(name); + + try { + return { + name, + symbol: erc20.symbol!, + decimals: erc20.decimals, + L1: L1Token ? tokenAddress : null, + L2: !L1Token ? tokenAddress : null, + image, + type: TokenType.ERC20, + UNKNOWN: null, + isDefault: false, + }; + } catch (err) { + log.error(err); + return; + } +} + +export async function getTokens(networkTypes: NetworkTypes): Promise { + try { + // Fetch the JSON data from the URL. + let url = process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST ? (process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST as string) : ""; + if (networkTypes === NetworkTypes.SEPOLIA) { + url = process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST ? (process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST as string) : ""; + } + + const response = await fetch(url); + const data = await response.json(); + const tokens = data.tokens; + const bridgedTokens = tokens.filter( + (token: Token) => token.tokenType.includes(CANONICAL_BRIDGED_TYPE) || token.symbol === USDC_TYPE, + ); + return bridgedTokens; + } catch (error) { + log.error("Error getTokens", { error }); + return []; + } +} diff --git a/bridge-ui/src/stores/configStore.ts b/bridge-ui/src/stores/configStore.ts index 8f06e542..dcc76b2e 100644 --- a/bridge-ui/src/stores/configStore.ts +++ b/bridge-ui/src/stores/configStore.ts @@ -4,6 +4,7 @@ import { createJSONStorage, persist } from "zustand/middleware"; export type ConfigState = { agreeToTerms: boolean; + rehydrated: boolean; }; export type ConfigActions = { @@ -14,6 +15,7 @@ export type ConfigStore = ConfigState & ConfigActions; export const defaultInitState: ConfigState = { agreeToTerms: false, + rehydrated: false, }; export const useConfigStore = create()( @@ -29,6 +31,11 @@ export const useConfigStore = create()( migrate: () => { return defaultInitState; }, + onRehydrateStorage: () => (state) => { + if (state) { + state.rehydrated = true; + } + }, }, ), ); diff --git a/bridge-ui/src/utils/chainsUtil.ts b/bridge-ui/src/utils/chainsUtil.ts index ef17f045..bebaade7 100644 --- a/bridge-ui/src/utils/chainsUtil.ts +++ b/bridge-ui/src/utils/chainsUtil.ts @@ -14,6 +14,19 @@ export const getChainNetworkLayer = (chain: Chain) => { return; }; +export const getChainNetworkLayerByChainId = (chainId: number) => { + switch (chainId) { + case linea.id: + case lineaSepolia.id: + return NetworkLayer.L2; + case mainnet.id: + case sepolia.id: + return NetworkLayer.L1; + } + + return; +}; + export const getChainNetworkType = (chain: Chain) => { switch (chain.id) { case linea.id: diff --git a/bridge-ui/src/utils/constants.ts b/bridge-ui/src/utils/constants.ts new file mode 100644 index 00000000..7ebde4fd --- /dev/null +++ b/bridge-ui/src/utils/constants.ts @@ -0,0 +1,38 @@ +import BridgeIcon from "@/assets/icons/bridge.svg"; +import TransactionsIcon from "@/assets/icons/transaction.svg"; +import DocsIcon from "@/assets/icons/docs.svg"; +import FaqIcon from "@/assets/icons/faq.svg"; + +export const MENU_ITEMS = [ + { + title: "Bridge", + href: "/", + external: false, + Icon: BridgeIcon, + }, + { + title: "Transactions", + href: "/transactions", + external: false, + Icon: TransactionsIcon, + }, + { + title: "Docs", + href: "https://docs.linea.build/", + external: true, + Icon: DocsIcon, + }, + { + title: "FAQ", + href: "https://support.linea.build/hc/en-us/categories/13281330249371-FAQs", + external: true, + Icon: FaqIcon, + }, +]; + +export const NETWORK_ID_TO_NAME: Record = { + 59144: "Linea", + 59141: "Linea Sepolia", + 1: "Ethereum", + 11155111: "Sepolia", +}; diff --git a/bridge-ui/src/utils/format.ts b/bridge-ui/src/utils/format.ts index eea891fc..d0788737 100644 --- a/bridge-ui/src/utils/format.ts +++ b/bridge-ui/src/utils/format.ts @@ -1,3 +1,4 @@ +import { formatDate, fromUnixTime } from "date-fns"; import { Address, getAddress } from "viem"; /** @@ -11,6 +12,17 @@ export const formatAddress = (address: string | undefined, step = 5) => { return address.substring(0, step) + "..." + address.substring(address.length - step, address.length); }; +/** + * Formats a hexadecimal string by truncating it and adding ellipsis in the middle. + * @param hexString - The hexadecimal string to format. + * @param step - The number of characters to keep at the beginning and end of the string. + * @returns The formatted hexadecimal string. + */ +export const formatHex = (hexString: string | undefined, step = 5) => { + if (!hexString) return "N/A"; + return hexString.substring(0, step) + "..." + hexString.substring(hexString.length - step, hexString.length); +}; + /** * Format balance * @param balance @@ -34,3 +46,13 @@ export const formatBalance = (balance: string | undefined, precision = 4) => { export const safeGetAddress = (address: Address | null): string | null => { return address ? getAddress(address) : null; }; + +/** + * Format timestamp + * @param timestamp + * @param formatStr + * @returns + */ +export const formatTimestamp = (timestamp: number, formatStr: string) => { + return formatDate(fromUnixTime(timestamp), formatStr); +}; diff --git a/bridge-ui/src/utils/transactionParsers/parseERC20Events.ts b/bridge-ui/src/utils/transactionParsers/parseERC20Events.ts index 1ce89b21..98f296e0 100644 --- a/bridge-ui/src/utils/transactionParsers/parseERC20Events.ts +++ b/bridge-ui/src/utils/transactionParsers/parseERC20Events.ts @@ -2,7 +2,7 @@ import { Chain, PublicClient, decodeAbiParameters, getAddress } from "viem"; import log from "loglevel"; import { NetworkTokens, NetworkType } from "@/config/config"; import { ERC20Event, ERC20V2Event } from "@/models"; -import fetchTokenInfo from "@/services/fetchTokenInfo"; +import { fetchTokenInfo } from "@/services"; import { TransactionHistory } from "@/models/history"; import { findTokenByAddress } from "./helpers"; diff --git a/bridge-ui/svg.d.ts b/bridge-ui/svg.d.ts new file mode 100644 index 00000000..6eb3cd9d --- /dev/null +++ b/bridge-ui/svg.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + import React from "react"; + export const ReactComponent: React.FunctionComponent>; +} diff --git a/bridge-ui/tailwind.config.ts b/bridge-ui/tailwind.config.ts index eb9f63da..939f5766 100644 --- a/bridge-ui/tailwind.config.ts +++ b/bridge-ui/tailwind.config.ts @@ -11,13 +11,12 @@ const config: Config = { ], theme: { extend: { - backgroundImage: { - hero: "url('/bridge_bg.png')", - }, colors: { primary: "#61DFFF", + secondary: "#FF62E6", card: "#505050", cardBg: "#1D1D1D", + success: "#C1FF14", }, fontFamily: { atypText: ["var(--font-atyp-text)"], @@ -31,8 +30,10 @@ const config: Config = { dark: { ...daisyuiThemes.dark, primary: "#61DFFF", + secondary: "#FF62E6", "primary-content": "#000000", - info: "#fff", + info: "#61DFFF", + success: "#C1FF14", }, }, ], diff --git a/bridge-ui/test/wallet-setup/metamask.setup.ts b/bridge-ui/test/wallet-setup/metamask.setup.ts index 94f65722..925acfe8 100644 --- a/bridge-ui/test/wallet-setup/metamask.setup.ts +++ b/bridge-ui/test/wallet-setup/metamask.setup.ts @@ -2,8 +2,10 @@ import { MetaMask, defineWalletSetup, getExtensionId } from "@synthetixio/synpre import { LINEA_SEPOLIA_NETWORK, METAMASK_PASSWORD, METAMASK_SEED_PHRASE, TEST_PRIVATE_KEY } from "../constants"; export default defineWalletSetup(METAMASK_PASSWORD, async (context, walletPage) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore const extensionId = await getExtensionId(context, "MetaMask"); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore const metamask = new MetaMask(context, walletPage, METAMASK_PASSWORD, extensionId); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba848024..97e162ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: dependencies: '@consensys/linea-sdk': specifier: 0.3.0 - version: 0.3.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(utf-8-validate@5.0.10) + version: 0.3.0(bufferutil@4.0.8)(ts-node@10.9.2(typescript@5.5.4))(utf-8-validate@5.0.10) '@headlessui/react': specifier: 2.1.2 version: 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -61,7 +61,7 @@ importers: version: 5.0.8(@types/react@18.3.3)(@wagmi/connectors@5.1.0(@types/react@18.3.3)(@wagmi/core@2.13.0(@tanstack/query-core@5.51.9)(@types/react@18.3.3)(react@18.3.1)(typescript@5.5.4)(viem@2.18.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.3(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.19.0)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.18.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8))(@wagmi/core@2.13.0(@tanstack/query-core@5.51.9)(@types/react@18.3.3)(react@18.3.1)(typescript@5.5.4)(viem@2.18.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@5.0.10)(viem@2.18.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.12.0(@tanstack/query-core@5.51.9)(@tanstack/react-query@5.51.11(react@18.3.1))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.3(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@4.19.0)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.18.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)) axios: specifier: 1.7.2 - version: 1.7.2(debug@4.3.5) + version: 1.7.2 classnames: specifier: 2.5.1 version: 2.5.1 @@ -74,9 +74,6 @@ importers: framer-motion: specifier: 11.3.17 version: 11.3.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - gray-matter: - specifier: 4.0.3 - version: 4.0.3 joi: specifier: 17.13.3 version: 17.13.3 @@ -129,9 +126,12 @@ importers: '@playwright/test': specifier: 1.45.3 version: 1.45.3 + '@svgr/webpack': + specifier: ^8.1.0 + version: 8.1.0(typescript@5.5.4) '@synthetixio/synpress': specifier: 4.0.0-alpha.7 - version: 4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8) '@types/fs-extra': specifier: 11.0.4 version: 11.0.4 @@ -155,16 +155,16 @@ importers: version: 14.2.5(eslint@9.7.0)(typescript@5.5.4) eslint-plugin-tailwindcss: specifier: 3.17.4 - version: 3.17.4(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))) + version: 3.17.4(tailwindcss@3.4.7(ts-node@10.9.2(typescript@5.5.4))) postcss: specifier: 8.4.40 version: 8.4.40 tailwind-scrollbar: specifier: 3.1.0 - version: 3.1.0(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))) + version: 3.1.0(tailwindcss@3.4.7(ts-node@10.9.2(typescript@5.5.4))) tailwindcss: specifier: 3.4.7 - version: 3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + version: 3.4.7(ts-node@10.9.2(typescript@5.5.4)) contracts: devDependencies: @@ -1042,12 +1042,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-constant-elements@7.25.1': + resolution: {integrity: sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-display-name@7.24.7': resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-development@7.24.7': + resolution: {integrity: sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.24.7': resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} engines: {node: '>=6.9.0'} @@ -1066,6 +1078,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-pure-annotations@7.24.7': + resolution: {integrity: sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-regenerator@7.24.7': resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} engines: {node: '>=6.9.0'} @@ -1161,6 +1179,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/preset-react@7.24.7': + resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} engines: {node: '>=6.9.0'} @@ -2838,6 +2862,84 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-svgo@8.1.0': + resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/webpack@8.1.0': + resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} + engines: {node: '>=14'} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -2899,6 +3001,10 @@ packages: '@tanstack/virtual-core@3.8.3': resolution: {integrity: sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ==} + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + '@ts-bridge/cli@0.1.4': resolution: {integrity: sha512-X8sgizUyAJGxwXKpCA3ovXW7ukKsaplL6CV0Q4O4TH4mS9sok0tpN7UsRsXm6CDXpe5DYZ4FWTimmlQB6QZTPw==} engines: {node: ^18.18 || >=20} @@ -3875,6 +3981,9 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} @@ -4289,6 +4398,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -4437,14 +4550,33 @@ packages: crypt@0.0.2: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-selector-tokenizer@0.8.0: resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==} + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -4687,9 +4819,25 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -4770,6 +4918,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -5164,10 +5316,6 @@ packages: ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -5614,10 +5762,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - gray-matter@4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} - h3@1.12.0: resolution: {integrity: sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==} @@ -5954,10 +6098,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -6610,6 +6750,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -6661,6 +6804,12 @@ packages: md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -7017,6 +7166,9 @@ packages: sass: optional: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + nocache@3.0.4: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} engines: {node: '>=12.0.0'} @@ -7124,6 +7276,9 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -8104,10 +8259,6 @@ packages: resolution: {integrity: sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==} engines: {node: '>=14.0.0'} - section-matter@1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} - secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -8244,6 +8395,9 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + socket.io-client@4.7.5: resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} engines: {node: '>=10.0.0'} @@ -8451,10 +8605,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-bom-string@1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} - strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -8531,6 +8681,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + swarm-js@0.1.42: resolution: {integrity: sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==} @@ -9697,7 +9855,7 @@ snapshots: '@babel/traverse': 7.24.8 '@babel/types': 7.24.9 convert-source-map: 2.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -9757,7 +9915,7 @@ snapshots: '@babel/core': 7.24.9 '@babel/helper-compilation-targets': 7.24.8 '@babel/helper-plugin-utils': 7.24.8 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -10353,11 +10511,23 @@ snapshots: '@babel/core': 7.24.9 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-constant-elements@7.25.1(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.9) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -10379,6 +10549,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -10564,6 +10740,18 @@ snapshots: '@babel/types': 7.24.9 esutils: 2.0.3 + '@babel/preset-react@7.24.7(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-validator-option': 7.24.8 + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.24.9) + transitivePeerDependencies: + - supports-color + '@babel/preset-typescript@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -10606,7 +10794,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.8 '@babel/types': 7.24.9 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10658,7 +10846,7 @@ snapshots: '@colors/colors@1.6.0': {} - '@consensys/linea-sdk@0.3.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(utf-8-validate@5.0.10)': + '@consensys/linea-sdk@0.3.0(bufferutil@4.0.8)(ts-node@10.9.2(typescript@5.5.4))(utf-8-validate@5.0.10)': dependencies: better-sqlite3: 9.6.0 class-validator: 0.14.1 @@ -10666,8 +10854,8 @@ snapshots: ethers: 6.13.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) lru-cache: 10.4.3 pg: 8.12.0 - typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) - typeorm-naming-strategies: 4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))) + typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(typescript@5.5.4)) + typeorm-naming-strategies: 4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(typescript@5.5.4))) winston: 3.13.1 transitivePeerDependencies: - '@google-cloud/spanner' @@ -10872,7 +11060,7 @@ snapshots: '@eslint/config-array@0.17.1': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -10894,7 +11082,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 espree: 10.1.0 globals: 14.0.0 ignore: 5.3.1 @@ -11516,7 +11704,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.7 + '@types/node': 20.14.12 '@types/yargs': 15.0.19 chalk: 4.1.2 @@ -11649,7 +11837,7 @@ snapshots: bufferutil: 4.0.8 cross-fetch: 4.0.0(encoding@0.1.13) date-fns: 2.30.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eciesjs: 0.3.19 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -11677,7 +11865,7 @@ snapshots: '@types/dom-screen-wake-lock': 1.0.3 bowser: 2.11.0 cross-fetch: 4.0.0(encoding@0.1.13) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eciesjs: 0.3.19 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -11709,7 +11897,7 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 semver: 7.6.3 superstruct: 1.0.4 transitivePeerDependencies: @@ -11722,7 +11910,7 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 pony-cause: 2.1.11 semver: 7.6.3 uuid: 9.0.1 @@ -11736,7 +11924,7 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 pony-cause: 2.1.11 semver: 7.6.3 uuid: 9.0.1 @@ -12809,6 +12997,99 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + + '@svgr/babel-preset@8.1.0(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.9) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.9) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.9) + + '@svgr/core@8.1.0(typescript@5.5.4)': + dependencies: + '@babel/core': 7.24.9 + '@svgr/babel-preset': 8.1.0(@babel/core@7.24.9) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.5.4) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.24.9 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.5.4))': + dependencies: + '@babel/core': 7.24.9 + '@svgr/babel-preset': 8.1.0(@babel/core@7.24.9) + '@svgr/core': 8.1.0(typescript@5.5.4) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.5.4))(typescript@5.5.4)': + dependencies: + '@svgr/core': 8.1.0(typescript@5.5.4) + cosmiconfig: 8.3.6(typescript@5.5.4) + deepmerge: 4.3.1 + svgo: 3.3.2 + transitivePeerDependencies: + - typescript + + '@svgr/webpack@8.1.0(typescript@5.5.4)': + dependencies: + '@babel/core': 7.24.9 + '@babel/plugin-transform-react-constant-elements': 7.25.1(@babel/core@7.24.9) + '@babel/preset-env': 7.24.8(@babel/core@7.24.9) + '@babel/preset-react': 7.24.7(@babel/core@7.24.9) + '@babel/preset-typescript': 7.24.7(@babel/core@7.24.9) + '@svgr/core': 8.1.0(typescript@5.5.4) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4))(typescript@5.5.4) + transitivePeerDependencies: + - supports-color + - typescript + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.12': @@ -12832,7 +13113,7 @@ snapshots: - utf-8-validate - zod - '@synthetixio/synpress-cache@0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4)': + '@synthetixio/synpress-cache@0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)': dependencies: axios: 1.6.7 chalk: 5.3.0 @@ -12843,7 +13124,7 @@ snapshots: gradient-string: 2.0.2 playwright-core: 1.45.3 progress: 2.0.3 - tsup: 8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4) + tsup: 8.0.2(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4) unzipper: 0.10.14 zod: 3.22.4 transitivePeerDependencies: @@ -12859,10 +13140,10 @@ snapshots: dependencies: '@playwright/test': 1.45.3 - '@synthetixio/synpress-metamask@0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)': + '@synthetixio/synpress-metamask@0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)': dependencies: '@playwright/test': 1.45.3 - '@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4) + '@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4) '@synthetixio/synpress-core': 0.0.1-alpha.7(@playwright/test@1.45.3) '@viem/anvil': 0.0.7(bufferutil@4.0.8)(utf-8-validate@5.0.10) fs-extra: 11.2.0 @@ -12879,13 +13160,13 @@ snapshots: - typescript - utf-8-validate - '@synthetixio/synpress@4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@synthetixio/synpress@4.0.0-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@playwright/test': 1.45.3 '@synthetixio/ethereum-wallet-mock': 0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8) - '@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4) + '@synthetixio/synpress-cache': 0.0.1-alpha.7(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4) '@synthetixio/synpress-core': 0.0.1-alpha.7(@playwright/test@1.45.3) - '@synthetixio/synpress-metamask': 0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + '@synthetixio/synpress-metamask': 0.0.1-alpha.7(@playwright/test@1.45.3)(bufferutil@4.0.8)(playwright-core@1.45.3)(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) transitivePeerDependencies: - '@microsoft/api-extractor' - '@swc/core' @@ -12922,6 +13203,8 @@ snapshots: '@tanstack/virtual-core@3.8.3': {} + '@trysound/sax@0.2.0': {} + '@ts-bridge/cli@0.1.4(@ts-bridge/shims@0.1.1)(typescript@5.5.4)': dependencies: chalk: 5.3.0 @@ -13089,7 +13372,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.12 '@types/node@12.20.55': {} @@ -13211,7 +13494,7 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eslint: 9.7.0 optionalDependencies: typescript: 5.5.4 @@ -13261,7 +13544,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -14486,7 +14769,15 @@ snapshots: axios@1.6.7: dependencies: - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axios@1.7.2: + dependencies: + follow-redirects: 1.15.6 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -14686,6 +14977,8 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@1.0.0: {} + bowser@2.11.0: {} boxen@5.1.2: @@ -14955,7 +15248,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.12 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -15134,6 +15427,8 @@ snapshots: commander@4.1.1: {} + commander@7.2.0: {} + commander@8.3.0: {} commander@9.5.0: {} @@ -15309,13 +15604,37 @@ snapshots: crypt@0.0.2: {} + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + css-selector-tokenizer@0.8.0: dependencies: cssesc: 3.0.0 fastparse: 1.1.2 + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.0 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.0 + + css-what@6.1.0: {} + cssesc@3.0.0: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + csstype@3.1.3: {} csv-parser@3.0.0: @@ -15386,6 +15705,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.5: + dependencies: + ms: 2.1.2 + debug@4.3.5(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -15532,8 +15855,31 @@ snapshots: dependencies: esutils: 2.0.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dom-walk@0.1.2: {} + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.6.3 + dotenv@16.4.5: {} duplexer2@0.1.4: @@ -15620,7 +15966,7 @@ snapshots: engine.io-client@6.5.4(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 engine.io-parser: 5.2.3 ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) xmlhttprequest-ssl: 2.0.0 @@ -15641,6 +15987,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + env-paths@2.2.1: {} envinfo@7.13.0: {} @@ -15863,7 +16211,7 @@ snapshots: eslint: 9.7.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@9.7.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5))(eslint@9.7.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.7.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@9.7.0) eslint-plugin-react: 7.35.0(eslint@9.7.0) eslint-plugin-react-hooks: 4.6.2(eslint@9.7.0) @@ -15887,11 +16235,11 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@9.7.0): dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 enhanced-resolve: 5.17.1 eslint: 9.7.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@9.7.0))(eslint@9.7.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5))(eslint@9.7.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.7.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -15913,17 +16261,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@9.7.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) - eslint: 9.7.0 - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5))(eslint@9.7.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.7.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -15933,7 +16271,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.7.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@9.7.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.7.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@9.7.0))(eslint@9.7.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -15944,7 +16282,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.2.0(eslint@9.7.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -16005,11 +16343,11 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-plugin-tailwindcss@3.17.4(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))): + eslint-plugin-tailwindcss@3.17.4(tailwindcss@3.4.7(ts-node@10.9.2(typescript@5.5.4))): dependencies: fast-glob: 3.3.2 postcss: 8.4.40 - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + tailwindcss: 3.4.7(ts-node@10.9.2(typescript@5.5.4)) eslint-scope@7.2.2: dependencies: @@ -16081,7 +16419,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -16453,10 +16791,6 @@ snapshots: dependencies: type: 2.7.3 - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - extend@3.0.2: {} extension-port-stream@3.0.0: @@ -16614,6 +16948,8 @@ snapshots: fn.name@1.1.0: {} + follow-redirects@1.15.6: {} + follow-redirects@1.15.6(debug@4.3.5): optionalDependencies: debug: 4.3.5(supports-color@8.1.1) @@ -16969,13 +17305,6 @@ snapshots: graphemer@1.4.0: {} - gray-matter@4.0.3: - dependencies: - js-yaml: 3.14.1 - kind-of: 6.0.3 - section-matter: 1.0.0 - strip-bom-string: 1.0.0 - h3@1.12.0: dependencies: cookie-es: 1.2.2 @@ -17225,7 +17554,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.6 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -17407,8 +17736,6 @@ snapshots: is-docker@3.0.0: {} - is-extendable@0.1.1: {} - is-extglob@2.1.1: {} is-finalizationregistry@1.0.2: @@ -18250,6 +18577,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lower-case@2.0.2: + dependencies: + tslib: 2.6.3 + lowercase-keys@2.0.0: {} lowercase-keys@3.0.0: {} @@ -18310,6 +18641,10 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + media-typer@0.3.0: {} memoize-one@5.2.1: {} @@ -18778,6 +19113,11 @@ snapshots: - '@babel/core' - babel-plugin-macros + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.6.3 + nocache@3.0.4: {} node-abi@3.65.0: @@ -18867,6 +19207,10 @@ snapshots: dependencies: path-key: 4.0.0 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + nullthrows@1.1.1: {} number-to-bn@1.7.0: @@ -19286,13 +19630,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.40 - postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4)): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 optionalDependencies: postcss: 8.4.40 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.5.4) + ts-node: 10.9.2(typescript@5.5.4) postcss-nested@6.2.0(postcss@8.4.40): dependencies: @@ -19949,11 +20293,6 @@ snapshots: node-addon-api: 5.1.0 node-gyp-build: 4.8.1 - section-matter@1.0.0: - dependencies: - extend-shallow: 2.0.1 - kind-of: 6.0.3 - secure-json-parse@2.7.0: {} selfsigned@2.4.1: @@ -20140,10 +20479,15 @@ snapshots: smart-buffer@4.2.0: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.6.3 + socket.io-client@4.7.5(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 engine.io-client: 6.5.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -20154,7 +20498,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -20432,8 +20776,6 @@ snapshots: dependencies: ansi-regex: 6.0.1 - strip-bom-string@1.0.0: {} - strip-bom@3.0.0: {} strip-bom@4.0.0: {} @@ -20491,6 +20833,18 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.0.1 + swarm-js@0.1.42(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: bluebird: 3.7.2 @@ -20535,11 +20889,11 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tailwind-scrollbar@3.1.0(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))): + tailwind-scrollbar@3.1.0(tailwindcss@3.4.7(ts-node@10.9.2(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + tailwindcss: 3.4.7(ts-node@10.9.2(typescript@5.5.4)) - tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)): + tailwindcss@3.4.7(ts-node@10.9.2(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -20558,7 +20912,7 @@ snapshots: postcss: 8.4.40 postcss-import: 15.1.0(postcss@8.4.40) postcss-js: 4.0.1(postcss@8.4.40) - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4)) postcss-nested: 6.2.0(postcss@8.4.40) postcss-selector-parser: 6.1.1 resolve: 1.22.8 @@ -20844,6 +21198,24 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.2(typescript@5.5.4): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + acorn: 8.12.1 + acorn-walk: 8.3.3 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -20859,17 +21231,17 @@ snapshots: tsort@0.0.1: {} - tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))(typescript@5.5.4): + tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4): dependencies: bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 esbuild: 0.19.12 execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(typescript@5.5.4)) resolve-from: 5.0.0 rollup: 4.19.0 source-map: 0.8.0-beta.0 @@ -20975,9 +21347,9 @@ snapshots: dependencies: typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.11.3)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) - typeorm-naming-strategies@4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4))): + typeorm-naming-strategies@4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(typescript@5.5.4))): dependencies: - typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)) + typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(typescript@5.5.4)) typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.3)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)): dependencies: @@ -21003,7 +21375,7 @@ snapshots: transitivePeerDependencies: - supports-color - typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.5.4)): + typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.12.0)(ts-node@10.9.2(typescript@5.5.4)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -21011,7 +21383,7 @@ snapshots: chalk: 4.1.2 cli-highlight: 2.1.11 dayjs: 1.11.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 dotenv: 16.4.5 glob: 10.4.5 mkdirp: 2.1.6 @@ -21023,7 +21395,7 @@ snapshots: optionalDependencies: better-sqlite3: 9.6.0 pg: 8.12.0 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.5.4) + ts-node: 10.9.2(typescript@5.5.4) transitivePeerDependencies: - supports-color