mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-06 22:23:55 -05:00
Feat: add lifi and small fixes (#798)
* fix: add gitattribute rules for woff2 files * feat: add lifi widget + fixes minor issues * fix: remove unused packages + clean constants declaration and config * fix: update dockerfile and github ci workflows * fix: env variable naming issue * fix: bridge mode alt value issue + remove button component
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -24,3 +24,5 @@ prover/prover-assets/**/**/**/*.bin binary
|
||||
prover/prover-assets/**/**/**/**/*.bin binary
|
||||
prover/prover-assets/kzgsrs/* binary
|
||||
|
||||
*.woff2 binary
|
||||
|
||||
|
||||
1
.github/workflows/bridge-ui-e2e-tests.yml
vendored
1
.github/workflows/bridge-ui-e2e-tests.yml
vendored
@@ -39,6 +39,7 @@ jobs:
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
|
||||
|
||||
- name: Install linux dependencies
|
||||
run: |
|
||||
|
||||
9
.github/workflows/bridge-ui-publish.yml
vendored
9
.github/workflows/bridge-ui-publish.yml
vendored
@@ -56,18 +56,19 @@ jobs:
|
||||
file: ./bridge-ui/Dockerfile
|
||||
push: ${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' }}
|
||||
tags: consensys/linea-bridge-ui:${{ env.DOCKER_TAG }}
|
||||
# Env file dedicated for dev
|
||||
build-args: |
|
||||
ENV_FILE=./bridge-ui/.env.production
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID=${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID=${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=${{ secrets.PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID=${{ env.NEXT_PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID=${{ env.NEXT_PUBLIC_INFURA_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=${{ env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY=${{ env.NEXT_PUBLIC_LIFI_API_KEY }}
|
||||
cache-from: type=registry,ref=consensys/linea-bridge-ui:buildcache
|
||||
cache-to: type=registry,ref=consensys/linea-bridge-ui:buildcache,mode=max
|
||||
env:
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
|
||||
|
||||
test-build:
|
||||
if: github.event.pull_request.head.repo.fork == true
|
||||
|
||||
@@ -7,7 +7,7 @@ NEXT_PUBLIC_MAINNET_LINEA_USDC_BRIDGE=0xA2Ee6Fce4ACB62D95448729cDb781e3BEb62504A
|
||||
NEXT_PUBLIC_MAINNET_GAS_ESTIMATED=100000
|
||||
NEXT_PUBLIC_MAINNET_DEFAULT_GAS_LIMIT_SURPLUS=6000
|
||||
NEXT_PUBLIC_MAINNET_PROFIT_MARGIN=2
|
||||
MAINNET_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-mainnet-token-shortlist.json
|
||||
NEXT_PUBLIC_MAINNET_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-mainnet-token-shortlist.json
|
||||
|
||||
NEXT_PUBLIC_SEPOLIA_L1_TOKEN_BRIDGE=0x5A0a48389BB0f12E5e017116c1105da97E129142
|
||||
NEXT_PUBLIC_SEPOLIA_LINEA_TOKEN_BRIDGE=0x93DcAdf238932e6e6a85852caC89cBd71798F463
|
||||
@@ -18,7 +18,7 @@ NEXT_PUBLIC_SEPOLIA_LINEA_USDC_BRIDGE=0x39fd5cF710314341d35f9Dca20c1daa059Acb843
|
||||
NEXT_PUBLIC_SEPOLIA_GAS_ESTIMATED=100000
|
||||
NEXT_PUBLIC_SEPOLIA_DEFAULT_GAS_LIMIT_SURPLUS=6000
|
||||
NEXT_PUBLIC_SEPOLIA_PROFIT_MARGIN=2
|
||||
SEPOLIA_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-sepolia-token-shortlist.json
|
||||
NEXT_PUBLIC_SEPOLIA_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-sepolia-token-shortlist.json
|
||||
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID=<GITHUB_SECRET>
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=<GITHUB_SECRET>
|
||||
|
||||
@@ -7,7 +7,7 @@ NEXT_PUBLIC_MAINNET_LINEA_USDC_BRIDGE=0xA2Ee6Fce4ACB62D95448729cDb781e3BEb62504A
|
||||
NEXT_PUBLIC_MAINNET_GAS_ESTIMATED=100000
|
||||
NEXT_PUBLIC_MAINNET_DEFAULT_GAS_LIMIT_SURPLUS=6000
|
||||
NEXT_PUBLIC_MAINNET_PROFIT_MARGIN=2
|
||||
MAINNET_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-mainnet-token-shortlist.json
|
||||
NEXT_PUBLIC_MAINNET_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-mainnet-token-shortlist.json
|
||||
|
||||
NEXT_PUBLIC_SEPOLIA_L1_TOKEN_BRIDGE=0x5A0a48389BB0f12E5e017116c1105da97E129142
|
||||
NEXT_PUBLIC_SEPOLIA_LINEA_TOKEN_BRIDGE=0x93DcAdf238932e6e6a85852caC89cBd71798F463
|
||||
@@ -18,7 +18,7 @@ NEXT_PUBLIC_SEPOLIA_LINEA_USDC_BRIDGE=0x39fd5cF710314341d35f9Dca20c1daa059Acb843
|
||||
NEXT_PUBLIC_SEPOLIA_GAS_ESTIMATED=100000
|
||||
NEXT_PUBLIC_SEPOLIA_DEFAULT_GAS_LIMIT_SURPLUS=6000
|
||||
NEXT_PUBLIC_SEPOLIA_PROFIT_MARGIN=2
|
||||
SEPOLIA_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-sepolia-token-shortlist.json
|
||||
NEXT_PUBLIC_SEPOLIA_TOKEN_LIST=https://raw.githubusercontent.com/Consensys/linea-token-list/main/json/linea-sepolia-token-shortlist.json
|
||||
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID=<YOUR__WALLET_CONNECT_ID>
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=<YOUR_DYNAMIC_ENVIRONMENT_ID>
|
||||
|
||||
@@ -9,9 +9,11 @@ FROM base AS builder
|
||||
ARG NEXT_PUBLIC_WALLET_CONNECT_ID
|
||||
ARG NEXT_PUBLIC_INFURA_ID
|
||||
ARG NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID
|
||||
ARG NEXT_PUBLIC_LIFI_API_KEY
|
||||
ENV NEXT_PUBLIC_WALLET_CONNECT_ID=$NEXT_PUBLIC_WALLET_CONNECT_ID
|
||||
ENV NEXT_PUBLIC_INFURA_ID=$NEXT_PUBLIC_INFURA_ID
|
||||
ENV NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=$NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID
|
||||
ENV NEXT_PUBLIC_LIFI_API_KEY=$NEXT_PUBLIC_LIFI_API_KEY
|
||||
ARG ENV_FILE
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@consensys/linea-sdk": "0.3.0",
|
||||
"@dynamic-labs/ethereum": "4.6.3",
|
||||
"@dynamic-labs/sdk-react-core": "4.6.3",
|
||||
"@dynamic-labs/wagmi-connector": "4.6.3",
|
||||
"@dynamic-labs/ethereum": "4.9.5",
|
||||
"@dynamic-labs/sdk-react-core": "4.9.5",
|
||||
"@dynamic-labs/wagmi-connector": "4.9.5",
|
||||
"@headlessui/react": "2.1.9",
|
||||
"@tanstack/react-query": "5.62.16",
|
||||
"@wagmi/connectors": "5.1.15",
|
||||
"@wagmi/core": "2.16.3",
|
||||
"@lifi/widget": "3.18.1",
|
||||
"@tanstack/react-query": "5.69.0",
|
||||
"@wagmi/connectors": "5.7.11",
|
||||
"@wagmi/core": "2.16.7",
|
||||
"auto-zustand-selectors-hook": "3.0.1",
|
||||
"clsx": "2.1.1",
|
||||
"date-fns": "4.1.0",
|
||||
@@ -35,14 +36,14 @@
|
||||
"loglevel": "1.9.2",
|
||||
"next": "14.2.24",
|
||||
"next-seo": "6.6.0",
|
||||
"pino-pretty": "11.2.2",
|
||||
"pino-pretty": "13.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-icons": "5.3.0",
|
||||
"sass": "1.83.3",
|
||||
"react-icons": "5.5.0",
|
||||
"sass": "1.86.0",
|
||||
"sharp": "0.33.5",
|
||||
"viem": "2.22.4",
|
||||
"wagmi": "2.14.6",
|
||||
"viem": "2.23.13",
|
||||
"wagmi": "2.14.15",
|
||||
"zod": "3.24.2",
|
||||
"zustand": "4.5.4"
|
||||
},
|
||||
@@ -53,9 +54,9 @@
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/react": "18.3.11",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"dotenv": "16.4.5",
|
||||
"autoprefixer": "10.4.21",
|
||||
"dotenv": "16.4.7",
|
||||
"eslint-config-next": "14.2.15",
|
||||
"postcss": "8.4.47"
|
||||
"postcss": "8.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
@@ -68,7 +68,12 @@ export default function FaqPage() {
|
||||
|
||||
<ul className={styles["list"]}>
|
||||
{faqList.map((faq, index) => (
|
||||
<FaqItem key={index} data={faq} isOpen={openIndex === index} onToggle={() => handleToggle(index)} />
|
||||
<FaqItem
|
||||
key={`faq-item-${index}`}
|
||||
data={faq}
|
||||
isOpen={openIndex === index}
|
||||
onToggle={() => handleToggle(index)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -21,11 +21,11 @@ const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" data-theme="v2">
|
||||
<html lang="en" data-theme="v2" className={clsx(atypFont.variable, atypTextFont.variable)}>
|
||||
<title>{metadata.title?.toString()}</title>
|
||||
<meta name="description" content={metadata.description?.toString()} key="desc" />
|
||||
|
||||
<body className={clsx(atypFont.variable, atypTextFont.variable, atypFont.className, atypTextFont.className)}>
|
||||
<body>
|
||||
<noscript dangerouslySetInnerHTML={{ __html: gtmNoScript }} />
|
||||
|
||||
<Providers>
|
||||
@@ -45,8 +45,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</svg>
|
||||
</body>
|
||||
|
||||
<Script id="usabilla" dangerouslySetInnerHTML={{ __html: usabillaBeScript }} />
|
||||
<Script id="gtm" dangerouslySetInnerHTML={{ __html: gtmScript }} />
|
||||
<Script id="usabilla" dangerouslySetInnerHTML={{ __html: usabillaBeScript }} strategy="lazyOnload" />
|
||||
<Script id="gtm" dangerouslySetInnerHTML={{ __html: gtmScript }} strategy="lazyOnload" />
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
10
bridge-ui/src/app/native-bridge/page.module.scss
Normal file
10
bridge-ui/src/app/native-bridge/page.module.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.content-wrapper {
|
||||
max-width: 29.25rem;
|
||||
margin: 0 auto 3.75rem;
|
||||
|
||||
width: calc(100% - 3rem);
|
||||
|
||||
@include bp("tablet") {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
21
bridge-ui/src/app/native-bridge/page.tsx
Normal file
21
bridge-ui/src/app/native-bridge/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import InternalNav from "@/components/internal-nav";
|
||||
import BridgeLayout from "@/components/bridge/bridge-layout";
|
||||
import styles from "./page.module.scss";
|
||||
import TopBanner from "@/components/top-banner";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<TopBanner
|
||||
text="Bridging USDC (USDC.e) is temporarily disabled until March 26. Learn more in our announcement here."
|
||||
href="https://x.com/LineaBuild/status/1901347758230958528"
|
||||
/>
|
||||
<section className={styles["content-wrapper"]}>
|
||||
<InternalNav />
|
||||
<BridgeLayout />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import InternalNav from "@/components/internal-nav";
|
||||
import BridgeLayout from "@/components/bridge/bridge-layout";
|
||||
import styles from "./page.module.scss";
|
||||
import TopBanner from "@/components/top-banner";
|
||||
import { Widget } from "@/components/lifi/widget";
|
||||
|
||||
export default function Home() {
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<TopBanner
|
||||
text="Bridging USDC (USDC.e) is temporarily disabled until March 26. Learn more in our announcement here."
|
||||
href="https://x.com/LineaBuild/status/1901347758230958528"
|
||||
/>
|
||||
<section className={styles["content-wrapper"]}>
|
||||
<InternalNav />
|
||||
<BridgeLayout />
|
||||
</section>
|
||||
</>
|
||||
<section className={styles["content-wrapper"]}>
|
||||
<InternalNav />
|
||||
<Widget />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,81 +3,26 @@ import localFont from "next/font/local";
|
||||
const atypFont = localFont({
|
||||
display: "swap",
|
||||
src: [
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Light-subset.woff2",
|
||||
weight: "300",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Light.woff2",
|
||||
weight: "300",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-LightItalic.woff2",
|
||||
weight: "300",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Regular-subset.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Regular.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Italic.woff2",
|
||||
weight: "400",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Medium-subset.woff2",
|
||||
weight: "500",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Medium.woff2",
|
||||
weight: "500",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-MediumItalic.woff2",
|
||||
weight: "500",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Semibold-subset.woff2",
|
||||
weight: "600",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Semibold.woff2",
|
||||
weight: "600",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-SemiboldItalic.woff2",
|
||||
weight: "600",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Bold-subset.woff2",
|
||||
weight: "700",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-Bold.woff2",
|
||||
weight: "700",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypDisplay-BoldItalic.woff2",
|
||||
weight: "700",
|
||||
style: "italic",
|
||||
},
|
||||
],
|
||||
variable: "--font-atyp",
|
||||
});
|
||||
|
||||
@@ -3,86 +3,26 @@ import localFont from "next/font/local";
|
||||
const atypTextFont = localFont({
|
||||
display: "swap",
|
||||
src: [
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Light-subset.woff2",
|
||||
weight: "300",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-LightItalic.woff2",
|
||||
weight: "300",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Light.woff2",
|
||||
weight: "300",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Regular-subset.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Regular.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Italic-subset.woff2",
|
||||
weight: "400",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Italic.woff2",
|
||||
weight: "400",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Medium-subset.woff2",
|
||||
weight: "500",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Medium.woff2",
|
||||
weight: "500",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-MediumItalic.woff2",
|
||||
weight: "500",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Semibold-subset.woff2",
|
||||
weight: "600",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Semibold.woff2",
|
||||
weight: "600",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-SemiboldItalic.woff2",
|
||||
weight: "600",
|
||||
style: "italic",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Bold-subset.woff2",
|
||||
weight: "700",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-Bold.woff2",
|
||||
weight: "700",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../../../public/fonts/AtypText-BoldItalic.woff2",
|
||||
weight: "700",
|
||||
style: "italic",
|
||||
},
|
||||
],
|
||||
variable: "--font-atyp-text",
|
||||
});
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useDynamicContext, useIsLoggedIn } from "@/lib/dynamic";
|
||||
import { useDynamicContext } from "@/lib/dynamic";
|
||||
import { useAccount } from "wagmi";
|
||||
import Bridge from "../form";
|
||||
import TransactionHistory from "../transaction-history";
|
||||
import { supportedChainIds } from "@/lib/wagmi";
|
||||
import { useTokens } from "@/hooks";
|
||||
import { useChainStore, FormStoreProvider, FormState, useNativeBridgeNavigationStore } from "@/stores";
|
||||
import { ChainLayer } from "@/types";
|
||||
import WrongNetwork from "../wrong-network";
|
||||
import BridgeSkeleton from "./skeleton";
|
||||
|
||||
export default function BridgeLayout() {
|
||||
const isTransactionHistoryOpen = useNativeBridgeNavigationStore.useIsTransactionHistoryOpen();
|
||||
const { chain, address } = useAccount();
|
||||
const isLoggedIn = useIsLoggedIn();
|
||||
const { address } = useAccount();
|
||||
const { sdkHasLoaded } = useDynamicContext();
|
||||
const tokens = useTokens();
|
||||
const fromChain = useChainStore.useFromChain();
|
||||
@@ -23,10 +20,6 @@ export default function BridgeLayout() {
|
||||
return <BridgeSkeleton />;
|
||||
}
|
||||
|
||||
if (isLoggedIn && (!chain?.id || !supportedChainIds.includes(chain.id))) {
|
||||
return <WrongNetwork />;
|
||||
}
|
||||
|
||||
if (isTransactionHistoryOpen) {
|
||||
return <TransactionHistory />;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
column-gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ export default function BridgeMode() {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<button type="button" className={styles.button}>
|
||||
<div className={styles.button}>
|
||||
<div className={styles["selected-label"]}>
|
||||
<Image src={logoSrc} width={16} height={16} alt="{label}" />
|
||||
<Image src={logoSrc} width={16} height={16} alt={label} />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function CurrencyDropdown({ disabled }: Props) {
|
||||
{isOpen && (
|
||||
<div ref={dropdownRef} className={styles.dropdown}>
|
||||
{supportedCurrencies.map((option) => (
|
||||
<div key={option.value} onClick={() => handleSelect(option)} className={styles.option}>
|
||||
<div key={`currency-${option.value}`} onClick={() => handleSelect(option)} className={styles.option}>
|
||||
<span className={styles.flag}>{option.flag}</span>
|
||||
{option.label}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ export function DestinationAddress() {
|
||||
<div className={styles["destination-address"]}>
|
||||
<div className={styles["headline"]}>
|
||||
<p className={styles.title}>Send to wallet</p>
|
||||
{address !== inputValue && !error && (
|
||||
{address !== inputValue && !error && isAddress(inputValue) && (
|
||||
<Link
|
||||
href={`${toChain.blockExplorers?.default.url ?? ""}/address/${inputValue}`}
|
||||
target="_blank"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Modal from "@/components/modal";
|
||||
|
||||
import styles from "./manual-claim.module.scss";
|
||||
import Link from "next/link";
|
||||
import Button from "@/components/ui/button";
|
||||
import { useNativeBridgeNavigationStore } from "@/stores";
|
||||
|
||||
type Props = {
|
||||
isModalOpen: boolean;
|
||||
@@ -10,12 +10,24 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function ManualClaim({ isModalOpen, onCloseModal }: Props) {
|
||||
const setIsTransactionHistoryOpen = useNativeBridgeNavigationStore.useSetIsTransactionHistoryOpen();
|
||||
|
||||
return (
|
||||
<Modal title="Manual claim on destination" isOpen={isModalOpen} onClose={onCloseModal}>
|
||||
<div className={styles["modal-inner"]}>
|
||||
<p className={styles["text"]}>
|
||||
You will need to claim your transaction on the destination chain with an additional transaction that requires
|
||||
ETH on the destination chain. This can be done on the <Link href="/transactions">Transaction page</Link>.
|
||||
ETH on the destination chain. This can be done on the{" "}
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => {
|
||||
setIsTransactionHistoryOpen(true);
|
||||
onCloseModal();
|
||||
}}
|
||||
>
|
||||
Transaction page
|
||||
</Button>
|
||||
.
|
||||
</p>
|
||||
<Button fullWidth onClick={onCloseModal}>
|
||||
OK
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
|
||||
a {
|
||||
button {
|
||||
font-size: 1rem;
|
||||
color: var(--v2-color-indigo);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function SelectNetwork({ isModalOpen, onCloseModal, onClick, netw
|
||||
filteredNetworks.map((network, index: number) => {
|
||||
return (
|
||||
<NetworkDetails
|
||||
key={index}
|
||||
key={`select-network-${index}`}
|
||||
name={network.name}
|
||||
onClickNetwork={() => {
|
||||
onClick(network);
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function ListTransaction({ transactions }: Props) {
|
||||
<>
|
||||
<ul className={styles["list"]}>
|
||||
{transactions.map((item, index) => (
|
||||
<Transaction key={`${item.bridgingTx}-${index}`} onClick={handleClickTransaction} {...item} />
|
||||
<Transaction key={`transaction-${item.bridgingTx}-${index}`} onClick={handleClickTransaction} {...item} />
|
||||
))}
|
||||
</ul>
|
||||
<TransactionDetails
|
||||
|
||||
@@ -4,11 +4,11 @@ import styles from "./skeleton-loader.module.scss";
|
||||
export default function SkeletonLoader() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className={styles.group}>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div key={`skeleton-item-${index}`} className={styles.group}>
|
||||
<div className={styles["skeleton-item"]}>
|
||||
{Array.from({ length: 2 }).map((_, i) => (
|
||||
<div key={i} className={styles["skeleton-group"]}>
|
||||
<div key={`skeleton-group-${i}`} className={styles["skeleton-group"]}>
|
||||
<div className={clsx(styles.skeleton, "pulsating")} />
|
||||
<div className={clsx(styles.skeleton, "pulsating")} />
|
||||
</div>
|
||||
|
||||
@@ -244,6 +244,22 @@ export const MENUS = [
|
||||
url: "https://linea.build/assets",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
__id: "01wv6FiyW8WmBwVSiELs0L",
|
||||
__typename: "link",
|
||||
name: "Privacy Policy",
|
||||
label: "Privacy Policy",
|
||||
url: "https://consensys.io/privacy-notice",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
__id: "01wv6FiyW8WmBwVSiELs0L",
|
||||
__typename: "link",
|
||||
name: "Terms of service",
|
||||
label: "Terms of service",
|
||||
url: "https://linea.build/terms-of-service",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
submenusRight: {
|
||||
__id: "SQOTqw3aK6kNo3oUGGh7W",
|
||||
|
||||
@@ -25,7 +25,7 @@ export const DesktopNavigation = ({ menus, theme = Theme.default }: Props) => {
|
||||
<nav className={styles["nav-wrapper"]}>
|
||||
<ul className={`${styles.navigation} ${styles[theme]}`}>
|
||||
{menus.map((menu, index) => (
|
||||
<MenuItem key={index} menu={filterMobileOnly(menu)} />
|
||||
<MenuItem key={`menu-item-${index}`} menu={filterMobileOnly(menu)} />
|
||||
))}
|
||||
<li>
|
||||
<HeaderConnect />
|
||||
@@ -87,8 +87,8 @@ function MenuItem({ menu }: MenuItemProps) {
|
||||
|
||||
{menu.submenusLeft && (
|
||||
<ul className={styles.submenu}>
|
||||
{menu.submenusLeft.map((submenu, key) => (
|
||||
<li className={styles.submenuItem} key={key}>
|
||||
{menu.submenusLeft.map((submenu, index) => (
|
||||
<li className={styles.submenuItem} key={`${menu.name}-submenuleft-{${index}`}>
|
||||
<Link href={submenu.url as string} target={submenu.external ? "_blank" : "_self"}>
|
||||
{submenu.label}
|
||||
{submenu.external && (
|
||||
@@ -102,7 +102,7 @@ function MenuItem({ menu }: MenuItemProps) {
|
||||
{menu.submenusRight && (
|
||||
<ul className={styles.right}>
|
||||
{menu.submenusRight?.submenusLeft?.map((submenu, subIndex) => (
|
||||
<li className={styles.submenuItem} key={subIndex}>
|
||||
<li className={styles.submenuItem} key={`${menu.name}-submenuright-submenuleft-${subIndex}`}>
|
||||
<Link
|
||||
href={submenu.url as string}
|
||||
target={submenu.external ? "_blank" : "_self"}
|
||||
|
||||
@@ -81,7 +81,7 @@ export const MobileNavigation = ({ menus, theme = "default" }: Props) => {
|
||||
{menus.map((menu, index) => (
|
||||
<li
|
||||
className={clsx(styles.menuItem, { [styles.active]: activeMenu === index })}
|
||||
key={index}
|
||||
key={`mobile-menu-item-${index}`}
|
||||
onClick={() => handleToggleMenu(menu, index)}
|
||||
>
|
||||
{menu.url ? (
|
||||
@@ -107,7 +107,7 @@ export const MobileNavigation = ({ menus, theme = "default" }: Props) => {
|
||||
{menu.submenusLeft.map((submenu, subIndex) => (
|
||||
<li
|
||||
className={styles.submenuItem}
|
||||
key={subIndex}
|
||||
key={`mobile-submenuleft-item-${subIndex}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleMenu(submenu, -1);
|
||||
@@ -128,7 +128,10 @@ export const MobileNavigation = ({ menus, theme = "default" }: Props) => {
|
||||
{menu.submenusRight && (
|
||||
<ul className={`${styles.submenu} ${styles.right}`}>
|
||||
{menu.submenusRight?.submenusLeft?.map((submenu, subIndex) => (
|
||||
<li className={styles.submenuItem} key={subIndex}>
|
||||
<li
|
||||
className={styles.submenuItem}
|
||||
key={`mobile-submenuright-submenuleft-item-${subIndex}`}
|
||||
>
|
||||
<Link
|
||||
href={submenu.url as string}
|
||||
target={submenu.external ? "_blank" : "_self"}
|
||||
|
||||
@@ -6,9 +6,13 @@ import NavItem from "./item";
|
||||
|
||||
const NavData = [
|
||||
{
|
||||
title: "Native Bridge",
|
||||
title: "All Bridges",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
title: "Native Bridge",
|
||||
href: "/native-bridge",
|
||||
},
|
||||
];
|
||||
|
||||
export default function InternalNav() {
|
||||
@@ -18,7 +22,7 @@ export default function InternalNav() {
|
||||
<div className={styles["wrapper"]}>
|
||||
<div className={styles["list-nav"]}>
|
||||
{NavData.map((item, index) => (
|
||||
<NavItem key={index} {...item} active={pathnane === item.href} />
|
||||
<NavItem key={`internal-nav-item-${index}`} {...item} active={pathnane === item.href} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,8 +45,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
src={"/images/illustration/illustration-mobile.svg"}
|
||||
role="presentation"
|
||||
alt="illustration mobile"
|
||||
width={428}
|
||||
height={359}
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: "100%", height: "auto", objectFit: "cover" }}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
@@ -69,6 +70,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
alt="illustration left"
|
||||
width={300}
|
||||
height={445}
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
className="right-illustration"
|
||||
@@ -77,14 +79,17 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
alt="illustration right"
|
||||
width={610}
|
||||
height={842}
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
className={clsx("mobile-illustration", { hidden: pathname === "/faq" })}
|
||||
src={"/images/illustration/illustration-mobile.svg"}
|
||||
role="presentation"
|
||||
alt="illustration mobile"
|
||||
width={428}
|
||||
height={359}
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: "100%", height: "auto", objectFit: "cover" }}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Web3Provider } from "@/contexts/web3.context";
|
||||
import { QueryProvider } from "@/contexts/query.context";
|
||||
import { TokenStoreProvider } from "@/stores";
|
||||
import { getTokenConfig } from "@/services/tokenService";
|
||||
|
||||
@@ -16,8 +17,10 @@ export async function Providers({ children }: ProvidersProps) {
|
||||
const tokensStoreInitialState = await getTokenStoreInitialState();
|
||||
|
||||
return (
|
||||
<Web3Provider>
|
||||
<TokenStoreProvider initialState={tokensStoreInitialState}>{children}</TokenStoreProvider>
|
||||
</Web3Provider>
|
||||
<QueryProvider>
|
||||
<Web3Provider>
|
||||
<TokenStoreProvider initialState={tokensStoreInitialState}>{children}</TokenStoreProvider>
|
||||
</Web3Provider>
|
||||
</QueryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
11
bridge-ui/src/components/lifi/client-only/index.tsx
Normal file
11
bridge-ui/src/components/lifi/client-only/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { type PropsWithChildren } from "react";
|
||||
import { useHydrated } from "@/hooks";
|
||||
|
||||
interface ClientOnlyProps extends PropsWithChildren {
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
|
||||
const hydrated = useHydrated();
|
||||
return hydrated ? children : fallback;
|
||||
}
|
||||
171
bridge-ui/src/components/lifi/widget/index.tsx
Normal file
171
bridge-ui/src/components/lifi/widget/index.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
"use client";
|
||||
|
||||
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
|
||||
import { ChainId, LiFiWidget, WidgetSkeleton, type WidgetConfig } from "@/lib/lifi";
|
||||
import { ClientOnly } from "../client-only";
|
||||
import atypTextFont from "@/assets/fonts/atypText";
|
||||
import { CHAINS_RPC_URLS } from "@/constants";
|
||||
import { config } from "@/config";
|
||||
|
||||
const widgetConfig: Partial<WidgetConfig> = {
|
||||
variant: "compact",
|
||||
subvariant: "default",
|
||||
appearance: "light",
|
||||
integrator: "Linea",
|
||||
theme: {
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#6119ef",
|
||||
},
|
||||
secondary: {
|
||||
main: "#6119ef",
|
||||
},
|
||||
background: {
|
||||
default: "#ffffff",
|
||||
paper: "#f8f7f2",
|
||||
},
|
||||
text: {
|
||||
primary: "#121212",
|
||||
secondary: "#525252",
|
||||
},
|
||||
grey: {
|
||||
200: "#f5f5f5",
|
||||
300: "#f1f1f1",
|
||||
700: "#525252",
|
||||
800: "#222222",
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 10,
|
||||
borderRadiusSecondary: 30,
|
||||
borderRadiusTertiary: 24,
|
||||
},
|
||||
typography: {
|
||||
fontFamily: atypTextFont.style.fontFamily,
|
||||
body1: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
body2: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
},
|
||||
container: {
|
||||
borderRadius: "0.625rem",
|
||||
maxHeight: "80vh",
|
||||
maxWidth: "29.25rem",
|
||||
minWidth: "none",
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
display: "flex",
|
||||
fontSize: "0.875rem",
|
||||
justifyContent: "flex-end",
|
||||
["p"]: {
|
||||
visibility: "hidden",
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
["p:before"]: {
|
||||
content: '""',
|
||||
visibility: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
},
|
||||
defaultProps: {
|
||||
variant: "elevation",
|
||||
},
|
||||
},
|
||||
MuiInputCard: "",
|
||||
},
|
||||
},
|
||||
hiddenUI: ["appearance", "language"],
|
||||
sdkConfig: {
|
||||
rpcUrls: {
|
||||
[ChainId.ETH]: [CHAINS_RPC_URLS[ChainId.ETH]],
|
||||
[ChainId.LNA]: [CHAINS_RPC_URLS[ChainId.LNA]],
|
||||
[ChainId.ARB]: [CHAINS_RPC_URLS[ChainId.ARB]],
|
||||
[ChainId.AVA]: [CHAINS_RPC_URLS[ChainId.AVA]],
|
||||
[ChainId.BAS]: [CHAINS_RPC_URLS[ChainId.BAS]],
|
||||
[ChainId.BLS]: [CHAINS_RPC_URLS[ChainId.BLS]],
|
||||
[ChainId.BSC]: [CHAINS_RPC_URLS[ChainId.BSC]],
|
||||
[ChainId.CEL]: [CHAINS_RPC_URLS[ChainId.CEL]],
|
||||
[ChainId.MNT]: [CHAINS_RPC_URLS[ChainId.MNT]],
|
||||
[ChainId.OPT]: [CHAINS_RPC_URLS[ChainId.OPT]],
|
||||
[ChainId.POL]: [CHAINS_RPC_URLS[ChainId.POL]],
|
||||
[ChainId.SCL]: [CHAINS_RPC_URLS[ChainId.SCL]],
|
||||
[ChainId.ERA]: [CHAINS_RPC_URLS[ChainId.ERA]],
|
||||
},
|
||||
},
|
||||
chains: {
|
||||
deny: [
|
||||
ChainId.BTC,
|
||||
ChainId.SOL,
|
||||
ChainId.PZE,
|
||||
ChainId.MOR,
|
||||
ChainId.FUS,
|
||||
ChainId.BOB,
|
||||
ChainId.MAM,
|
||||
ChainId.LSK,
|
||||
ChainId.UNI,
|
||||
ChainId.IMX,
|
||||
ChainId.GRA,
|
||||
ChainId.TAI,
|
||||
ChainId.SOE,
|
||||
ChainId.FRA,
|
||||
ChainId.ABS,
|
||||
ChainId.RSK,
|
||||
ChainId.WCC,
|
||||
ChainId.BER,
|
||||
ChainId.KAI,
|
||||
],
|
||||
},
|
||||
bridges: {
|
||||
allow: ["stargateV2", "stargateV2Bus", "across", "hop", "squid", "relay"],
|
||||
},
|
||||
apiKey: config.lifiApiKey,
|
||||
};
|
||||
|
||||
export function Widget() {
|
||||
const { setShowAuthFlow } = useDynamicContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ClientOnly fallback={<WidgetSkeleton config={widgetConfig} />}>
|
||||
<LiFiWidget
|
||||
config={{
|
||||
...widgetConfig,
|
||||
walletConfig: {
|
||||
onConnect() {
|
||||
setShowAuthFlow(true);
|
||||
},
|
||||
},
|
||||
}}
|
||||
integrator="linea"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import UnionIcon from "@/assets/icons/union.svg";
|
||||
import LeftIllustration from "./illustration/left.svg";
|
||||
import RightIllustration from "./illustration/right.svg";
|
||||
import CloseIcon from "@/assets/icons/x-circle.svg";
|
||||
import styles from "./top-banner.module.scss";
|
||||
import Image from "next/image";
|
||||
|
||||
type Props = {
|
||||
text: string;
|
||||
@@ -22,7 +21,16 @@ export default function TopBanner({ text, href }: Props) {
|
||||
|
||||
return (
|
||||
<div className={styles["banner-wrapper"]}>
|
||||
<LeftIllustration className={styles["left-illustration"]} />
|
||||
<Image
|
||||
className={styles["left-illustration"]}
|
||||
src={"/images/illustration/banner/left.svg"}
|
||||
role="presentation"
|
||||
alt="banner illustration left"
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: "56px", height: "100%" }}
|
||||
priority
|
||||
/>
|
||||
<div className={styles["banner"]}>
|
||||
<Link href={href} target="_blank" rel="noopener noreferrer" className={styles["inner"]} passHref>
|
||||
<span>{text}</span>
|
||||
@@ -30,7 +38,16 @@ export default function TopBanner({ text, href }: Props) {
|
||||
</Link>
|
||||
</div>
|
||||
<CloseIcon onClick={handleClose} className={styles["close-icon"]} />
|
||||
<RightIllustration className={styles["right-illustration"]} />
|
||||
<Image
|
||||
className={styles["right-illustration"]}
|
||||
src={"/images/illustration/banner/right.svg"}
|
||||
role="presentation"
|
||||
alt="banner illustration right"
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: "221px", height: "100%" }}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,12 +23,19 @@ const chainConfigSchema = z.object({
|
||||
export const configSchema = z
|
||||
.object({
|
||||
chains: z.record(z.string().regex(/^\d+$/), chainConfigSchema),
|
||||
walletConnectId: z.string(),
|
||||
walletConnectId: z.string().nonempty(),
|
||||
storage: z.object({
|
||||
minVersion: z.number().positive().int(),
|
||||
}),
|
||||
// Feature toggle for CCTPV2 for USDC transfers
|
||||
isCctpEnabled: z.boolean(),
|
||||
infuraApiKey: z.string().nonempty(),
|
||||
dynamicEnvironmentId: z.string().nonempty(),
|
||||
lifiApiKey: z.string().nonempty(),
|
||||
tokenListUrls: z.object({
|
||||
mainnet: z.string().trim().url(),
|
||||
sepolia: z.string().trim().url(),
|
||||
}),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -66,6 +66,13 @@ export const config: Config = {
|
||||
minVersion: process.env.NEXT_PUBLIC_STORAGE_MIN_VERSION ? parseInt(process.env.NEXT_PUBLIC_STORAGE_MIN_VERSION) : 1,
|
||||
},
|
||||
isCctpEnabled: process.env.NEXT_PUBLIC_IS_CCTP_ENABLED === "true",
|
||||
infuraApiKey: process.env.NEXT_PUBLIC_INFURA_ID ?? "",
|
||||
dynamicEnvironmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID ?? "",
|
||||
lifiApiKey: process.env.NEXT_PUBLIC_LIFI_API_KEY ?? "",
|
||||
tokenListUrls: {
|
||||
mainnet: process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST ?? "",
|
||||
sepolia: process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST ?? "",
|
||||
},
|
||||
};
|
||||
|
||||
export async function getConfiguration(): Promise<Config> {
|
||||
|
||||
8
bridge-ui/src/constants/cctp.ts
Normal file
8
bridge-ui/src/constants/cctp.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// Current fast transfer route fee for mainnet
|
||||
export const CCTP_TRANSFER_MAX_FEE_FALLBACK = 100n;
|
||||
// 1000 Fast transfer, 2000 Standard transfer
|
||||
export const CCTP_MIN_FINALITY_THRESHOLD = 1000;
|
||||
// https://developers.circle.com/stablecoins/message-format, add 2 for '0x' prefix
|
||||
export const CCTP_V2_MESSAGE_HEADER_LENGTH = 298;
|
||||
export const CCTP_V2_EXPIRATION_BLOCK_OFFSET = 2 + 344 * 2;
|
||||
export const CCTP_V2_EXPIRATION_BLOCK_LENGTH = 64;
|
||||
85
bridge-ui/src/constants/chains.ts
Normal file
85
bridge-ui/src/constants/chains.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { config } from "@/config";
|
||||
import {
|
||||
arbitrum,
|
||||
aurora,
|
||||
avalanche,
|
||||
base,
|
||||
blast,
|
||||
bsc,
|
||||
celo,
|
||||
cronos,
|
||||
fantom,
|
||||
gnosis,
|
||||
ink,
|
||||
linea,
|
||||
lineaSepolia,
|
||||
mainnet,
|
||||
mantle,
|
||||
mode,
|
||||
moonbeam,
|
||||
optimism,
|
||||
polygon,
|
||||
scroll,
|
||||
sei,
|
||||
sepolia,
|
||||
sonic,
|
||||
zksync,
|
||||
} from "viem/chains";
|
||||
|
||||
export const CHAINS = [
|
||||
mainnet,
|
||||
sepolia,
|
||||
linea,
|
||||
lineaSepolia,
|
||||
arbitrum,
|
||||
aurora,
|
||||
avalanche,
|
||||
base,
|
||||
blast,
|
||||
bsc,
|
||||
celo,
|
||||
cronos,
|
||||
fantom,
|
||||
gnosis,
|
||||
ink,
|
||||
mantle,
|
||||
mode,
|
||||
moonbeam,
|
||||
optimism,
|
||||
polygon,
|
||||
scroll,
|
||||
sei,
|
||||
sonic,
|
||||
zksync,
|
||||
] as const;
|
||||
|
||||
export const CHAINS_IDS = CHAINS.map((chain) => chain.id);
|
||||
|
||||
export const CHAINS_RPC_URLS: Record<(typeof CHAINS_IDS)[number], string> = {
|
||||
[mainnet.id]: `https://mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[sepolia.id]: `https://sepolia.infura.io/v3/${config.infuraApiKey}`,
|
||||
[linea.id]: `https://linea-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[lineaSepolia.id]: `https://linea-sepolia.infura.io/v3/${config.infuraApiKey}`,
|
||||
[arbitrum.id]: `https://arbitrum-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[aurora.id]: `https://mainnet.aurora.dev`,
|
||||
[avalanche.id]: `https://avalanche-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[base.id]: `https://base-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[blast.id]: `https://blast-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[bsc.id]: `https://bsc-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[celo.id]: `https://celo-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[cronos.id]: `https://evm.cronos.org`,
|
||||
[fantom.id]: `https://rpc.ankr.com/fantom`,
|
||||
[gnosis.id]: `https://rpc.gnosischain.com`,
|
||||
[ink.id]: `https://rpc-gel.inkonchain.com`,
|
||||
[mantle.id]: `https://mantle-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[mode.id]: `https://mainnet.mode.network`,
|
||||
[moonbeam.id]: `https://rpc.testnet.moonbeam.network`,
|
||||
[optimism.id]: `https://optimism-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[polygon.id]: `https://polygon-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[scroll.id]: `https://scroll-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[sei.id]: `https://evm-rpc.sei-apis.com`,
|
||||
[sonic.id]: `https://rpc.soniclabs.com`,
|
||||
[zksync.id]: `https://zksync-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
};
|
||||
|
||||
export const NATIVE_BRIDGE_SUPPORTED_CHAIN_IDS = [mainnet.id, linea.id, lineaSepolia.id, sepolia.id] as const;
|
||||
4
bridge-ui/src/constants/index.ts
Normal file
4
bridge-ui/src/constants/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./chains";
|
||||
export * from "./tokens";
|
||||
export * from "./message";
|
||||
export * from "./cctp";
|
||||
1
bridge-ui/src/constants/message.ts
Normal file
1
bridge-ui/src/constants/message.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const INBOX_L1L2_MESSAGE_STATUS_MAPPING_SLOT = 176n;
|
||||
1
bridge-ui/src/constants/tokens.ts
Normal file
1
bridge-ui/src/constants/tokens.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const USDC_SYMBOL = "USDC";
|
||||
14
bridge-ui/src/contexts/query.context.tsx
Normal file
14
bridge-ui/src/contexts/query.context.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
type Web3ProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export function QueryProvider({ children }: Web3ProviderProps) {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { DynamicWagmiConnector, EthereumWalletConnectors, DynamicContextProvider } from "@/lib/dynamic";
|
||||
import { ReactNode } from "react";
|
||||
import { config } from "@/lib/wagmi";
|
||||
import { WagmiProvider } from "wagmi";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
import { DynamicWagmiConnector, EthereumWalletConnectors, DynamicContextProvider } from "@/lib/dynamic";
|
||||
import { config as wagmiConfig } from "@/lib/wagmi";
|
||||
import { config } from "@/config";
|
||||
|
||||
type Web3ProviderProps = {
|
||||
children: ReactNode;
|
||||
@@ -65,17 +63,15 @@ export function Web3Provider({ children }: Web3ProviderProps) {
|
||||
return (
|
||||
<DynamicContextProvider
|
||||
settings={{
|
||||
environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID!,
|
||||
environmentId: config.dynamicEnvironmentId,
|
||||
walletConnectors: [EthereumWalletConnectors],
|
||||
mobileExperience: "redirect",
|
||||
appName: "Linea Bridge",
|
||||
cssOverrides,
|
||||
}}
|
||||
>
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<DynamicWagmiConnector>{children}</DynamicWagmiConnector>
|
||||
</QueryClientProvider>
|
||||
<WagmiProvider config={wagmiConfig}>
|
||||
<DynamicWagmiConnector>{children}</DynamicWagmiConnector>
|
||||
</WagmiProvider>
|
||||
</DynamicContextProvider>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEstimateFeesPerGas, useWatchBlockNumber } from "wagmi";
|
||||
import { SupportedChainId } from "@/lib/wagmi";
|
||||
import { SupportedChainIds } from "@/types";
|
||||
|
||||
const useFeeData = (chainId: SupportedChainId) => {
|
||||
const useFeeData = (chainId: SupportedChainIds) => {
|
||||
const { data, refetch } = useEstimateFeesPerGas({ chainId, type: "eip1559" });
|
||||
|
||||
useWatchBlockNumber({
|
||||
|
||||
@@ -16,3 +16,4 @@ export { default as useTokenBalance } from "./useTokenBalance";
|
||||
export { default as useTokenPrices } from "./useTokenPrices";
|
||||
export { default as useTokens } from "./useTokens";
|
||||
export { default as useTransactionHistory } from "./useTransactionHistory";
|
||||
export { default as useHydrated } from "./useHydrated";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useChainStore } from "@/stores";
|
||||
import { getCctpFee } from "@/services/cctp";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { CCTP_TRANSFER_MAX_FEE_FALLBACK } from "@/utils/cctp";
|
||||
import { CCTP_TRANSFER_MAX_FEE_FALLBACK } from "@/constants";
|
||||
|
||||
const useCctpSrcDomain = () => {
|
||||
const fromChain = useChainStore.useFromChain();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { encodeFunctionData, padHex, zeroHash } from "viem";
|
||||
import { useFormStore, useChainStore } from "@/stores";
|
||||
import { isCctp } from "@/utils/tokens";
|
||||
import { useCctpFee, useCctpDestinationDomain } from "./useCctpUtilHooks";
|
||||
import { CCTP_MIN_FINALITY_THRESHOLD } from "@/utils";
|
||||
import { CCTP_MIN_FINALITY_THRESHOLD } from "@/constants";
|
||||
|
||||
type UseDepositForBurnTxArgs = {
|
||||
allowance?: bigint;
|
||||
|
||||
15
bridge-ui/src/hooks/useHydrated.ts
Normal file
15
bridge-ui/src/hooks/useHydrated.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
function subscribe() {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const useHydrated = () => {
|
||||
return useSyncExternalStore(
|
||||
subscribe,
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
};
|
||||
|
||||
export default useHydrated;
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import { LineaSDK, Network } from "@consensys/linea-sdk";
|
||||
import { linea, lineaSepolia, mainnet, sepolia } from "viem/chains";
|
||||
import { L1MessageServiceContract, L2MessageServiceContract } from "@consensys/linea-sdk/dist/lib/contracts";
|
||||
import { useChainStore } from "@/stores";
|
||||
import { CHAINS_RPC_URLS } from "@/constants";
|
||||
|
||||
export interface LineaSDKContracts {
|
||||
L1: L1MessageServiceContract;
|
||||
@@ -15,11 +17,11 @@ const useLineaSDK = () => {
|
||||
let l1RpcUrl;
|
||||
let l2RpcUrl;
|
||||
if (fromChain.testnet) {
|
||||
l1RpcUrl = `https://sepolia.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`;
|
||||
l2RpcUrl = `https://linea-sepolia.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`;
|
||||
l1RpcUrl = CHAINS_RPC_URLS[sepolia.id];
|
||||
l2RpcUrl = CHAINS_RPC_URLS[lineaSepolia.id];
|
||||
} else {
|
||||
l1RpcUrl = `https://mainnet.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`;
|
||||
l2RpcUrl = `https://linea-mainnet.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`;
|
||||
l1RpcUrl = CHAINS_RPC_URLS[mainnet.id];
|
||||
l2RpcUrl = CHAINS_RPC_URLS[linea.id];
|
||||
}
|
||||
|
||||
const sdk = new LineaSDK({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
import { useChainStore, useTokenStore } from "@/stores";
|
||||
import { ChainLayer, Token } from "@/types";
|
||||
import { config } from "@/config";
|
||||
import { USDC_SYMBOL } from "@/utils";
|
||||
import { USDC_SYMBOL } from "@/constants";
|
||||
|
||||
const useTokens = (): Token[] => {
|
||||
const tokensList = useTokenStore((state) => state.tokensList);
|
||||
|
||||
3
bridge-ui/src/lib/lifi.ts
Normal file
3
bridge-ui/src/lib/lifi.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
|
||||
export * from "@lifi/widget";
|
||||
@@ -1,22 +1,26 @@
|
||||
import { http, createConfig } from "wagmi";
|
||||
import { linea, lineaSepolia, mainnet, sepolia } from "wagmi/chains";
|
||||
|
||||
export const chains = [mainnet, linea, lineaSepolia, sepolia] as const;
|
||||
export const supportedChainIds = [mainnet.id, linea.id, lineaSepolia.id, sepolia.id] as const;
|
||||
|
||||
export type SupportedChainId = (typeof supportedChainIds)[number];
|
||||
import { CHAINS, CHAINS_IDS, CHAINS_RPC_URLS } from "@/constants";
|
||||
|
||||
export const config = createConfig({
|
||||
chains,
|
||||
chains: CHAINS,
|
||||
multiInjectedProviderDiscovery: false,
|
||||
transports: {
|
||||
[mainnet.id]: http(`https://mainnet.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`, { batch: true }),
|
||||
[sepolia.id]: http(`https://sepolia.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`, { batch: true }),
|
||||
[linea.id]: http(`https://linea-mainnet.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`, { batch: true }),
|
||||
[lineaSepolia.id]: http(`https://linea-sepolia.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_ID}`, { batch: true }),
|
||||
},
|
||||
transports: generateWagmiTransports(CHAINS_IDS),
|
||||
});
|
||||
|
||||
function generateWagmiTransports(chainIds: (typeof CHAINS_IDS)[number][]) {
|
||||
return chainIds.reduce(
|
||||
(acc, chainId) => {
|
||||
acc[chainId] = generateWagmiTransport(chainId);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<(typeof chainIds)[number], ReturnType<typeof generateWagmiTransport>>,
|
||||
);
|
||||
}
|
||||
|
||||
function generateWagmiTransport(chainId: (typeof CHAINS_IDS)[number]) {
|
||||
return http(CHAINS_RPC_URLS[chainId], { batch: true });
|
||||
}
|
||||
|
||||
declare module "wagmi" {
|
||||
interface Register {
|
||||
config: typeof config;
|
||||
|
||||
@@ -12,9 +12,9 @@ enum NetworkTypes {
|
||||
export async function getTokens(networkTypes: NetworkTypes): Promise<GithubTokenListToken[]> {
|
||||
try {
|
||||
// Fetch the JSON data from the URL.
|
||||
let url = process.env.MAINNET_TOKEN_LIST ? (process.env.MAINNET_TOKEN_LIST as string) : "";
|
||||
let url = config.tokenListUrls.mainnet;
|
||||
if (networkTypes === NetworkTypes.SEPOLIA) {
|
||||
url = process.env.SEPOLIA_TOKEN_LIST ? (process.env.SEPOLIA_TOKEN_LIST as string) : "";
|
||||
url = config.tokenListUrls.sepolia;
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { SupportedChainId } from "@/lib/wagmi";
|
||||
import { NATIVE_BRIDGE_SUPPORTED_CHAIN_IDS } from "@/constants";
|
||||
import { Address } from "viem";
|
||||
|
||||
export type SupportedChainIds = (typeof NATIVE_BRIDGE_SUPPORTED_CHAIN_IDS)[number];
|
||||
|
||||
export enum ChainLayer {
|
||||
L1 = "L1",
|
||||
L2 = "L2",
|
||||
}
|
||||
|
||||
export type Chain = {
|
||||
id: SupportedChainId;
|
||||
id: SupportedChainIds;
|
||||
name: string;
|
||||
iconPath: string;
|
||||
nativeCurrency: { name: string; symbol: string; decimals: number };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { type LinkBlock, type AssetType, Theme } from "./ui";
|
||||
export { type Chain, ChainLayer } from "./chain";
|
||||
export { type Chain, ChainLayer, type SupportedChainIds } from "./chain";
|
||||
export { type TransactionType, TransactionStatus } from "./transaction";
|
||||
export { type Token, type GithubTokenListToken, type NetworkTokens } from "./token";
|
||||
export { BridgeProvider } from "./providers";
|
||||
|
||||
@@ -4,15 +4,11 @@ import { GetPublicClientReturnType } from "@wagmi/core";
|
||||
import { fetchCctpAttestationByTxHash, reattestCctpV2PreFinalityMessage } from "@/services/cctp";
|
||||
import { getPublicClient } from "@wagmi/core";
|
||||
import { config as wagmiConfig } from "@/lib/wagmi";
|
||||
|
||||
// Current fast transfer route fee for mainnet
|
||||
export const CCTP_TRANSFER_MAX_FEE_FALLBACK = 100n;
|
||||
// 1000 Fast transfer, 2000 Standard transfer
|
||||
export const CCTP_MIN_FINALITY_THRESHOLD = 1000;
|
||||
// https://developers.circle.com/stablecoins/message-format, add 2 for '0x' prefix
|
||||
const CCTP_V2_MESSAGE_HEADER_LENGTH = 298;
|
||||
const CCTP_V2_EXPIRATION_BLOCK_OFFSET = 2 + 344 * 2;
|
||||
const CCTP_V2_EXPIRATION_BLOCK_LENGTH = 64;
|
||||
import {
|
||||
CCTP_V2_EXPIRATION_BLOCK_LENGTH,
|
||||
CCTP_V2_EXPIRATION_BLOCK_OFFSET,
|
||||
CCTP_V2_MESSAGE_HEADER_LENGTH,
|
||||
} from "@/constants";
|
||||
|
||||
const isCctpNonceUsed = async (
|
||||
client: GetPublicClientReturnType,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Address } from "viem";
|
||||
import { linea, mainnet, Chain as ViemChain, sepolia, lineaSepolia } from "viem/chains";
|
||||
import { SupportedChainId } from "@/lib/wagmi";
|
||||
import { config } from "@/config";
|
||||
import { Chain, ChainLayer } from "@/types";
|
||||
import { Chain, ChainLayer, SupportedChainIds } from "@/types";
|
||||
|
||||
export const generateChain = (chain: ViemChain): Chain => {
|
||||
return {
|
||||
id: chain.id as SupportedChainId,
|
||||
id: chain.id as SupportedChainIds,
|
||||
name: chain.id !== lineaSepolia.id ? chain.name : "Linea Sepolia",
|
||||
iconPath: config.chains[chain.id].iconPath,
|
||||
nativeCurrency: chain.nativeCurrency,
|
||||
blockExplorers: chain.blockExplorers,
|
||||
// Possibly the wrong assumption to fallback to 'false', but fallback to 'true' makes the app crash mysteriously
|
||||
testnet: Boolean(chain.testnet),
|
||||
layer: getChainNetworkLayer(chain.id),
|
||||
messageServiceAddress: config.chains[chain.id].messageServiceAddress as Address,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { BridgeTransaction } from "@/types";
|
||||
import { SupportedChainId } from "@/lib/wagmi";
|
||||
import { BridgeTransaction, SupportedChainIds } from "@/types";
|
||||
|
||||
export function getCompleteTxStoreKeyForTx(transaction: BridgeTransaction): string {
|
||||
return getCompleteTxStoreKey(transaction.fromChain.id, transaction.bridgingTx);
|
||||
}
|
||||
|
||||
export function getCompleteTxStoreKey(fromChainId: SupportedChainId, bridgingTxHash: string): string {
|
||||
export function getCompleteTxStoreKey(fromChainId: SupportedChainIds, bridgingTxHash: string): string {
|
||||
return `${fromChainId}-${bridgingTxHash}`;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,6 @@ export { estimateEthGasFee, estimateERC20GasFee } from "./fees";
|
||||
export { formatAddress, formatBalance, formatHex, formatTimestamp, safeGetAddress } from "./format";
|
||||
export { fetchTransactionsHistory } from "./history";
|
||||
export { computeMessageHash, computeMessageStorageSlot, isCctpV2BridgeMessage, isNativeBridgeMessage } from "./message";
|
||||
export { isEth, isCctp, USDC_SYMBOL } from "./tokens";
|
||||
export { isEth, isCctp } from "./tokens";
|
||||
export { isEmptyObject } from "./utils";
|
||||
export {
|
||||
CCTP_TRANSFER_MAX_FEE_FALLBACK,
|
||||
CCTP_MIN_FINALITY_THRESHOLD,
|
||||
getCctpTransactionStatus,
|
||||
getCctpMessageByTxHash,
|
||||
} from "./cctp";
|
||||
export { getCctpTransactionStatus, getCctpMessageByTxHash } from "./cctp";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { keccak256, encodeAbiParameters, Address } from "viem";
|
||||
import { CctpV2BridgeMessage, NativeBridgeMessage } from "@/types";
|
||||
|
||||
const INBOX_L1L2_MESSAGE_STATUS_MAPPING_SLOT = 176n;
|
||||
import { INBOX_L1L2_MESSAGE_STATUS_MAPPING_SLOT } from "@/constants";
|
||||
|
||||
export function computeMessageHash(
|
||||
from: Address,
|
||||
|
||||
@@ -19,5 +19,3 @@ export const isCctp = (token: Token) => {
|
||||
isAddress(token.L2)
|
||||
);
|
||||
};
|
||||
|
||||
export const USDC_SYMBOL = "USDC";
|
||||
|
||||
3556
pnpm-lock.yaml
generated
3556
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user