feat: restructure and integrate layerswap (#953)

* feat: restructure and integrate layerswap

* fix: remove CEX page from internal nav because it is a duplicate

* fix: update layerswap widget and clean libs

* fix: wagmi connection

* chore: update color and layout

* fix: bump layerswap widget lib to version 0.1.15

* fix: fix widget width

* fix: fix ui issue on mobile

* fix: bump bridge ui version, update release notes and update dockerfile and ci

---------

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Co-authored-by: VGau <victorien.gauch@consensys.net>
This commit is contained in:
viphan007
2025-05-13 16:37:58 +07:00
committed by GitHub
parent b0ea13b957
commit fc5ed79ff4
37 changed files with 569 additions and 208 deletions

View File

@@ -60,6 +60,7 @@ jobs:
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
NEXT_PUBLIC_LIFI_INTEGRATOR_NAME: ${{ secrets.PUBLIC_LIFI_INTEGRATOR_NAME }}
NEXT_PUBLIC_ONRAMPER_API_KEY: ${{ secrets.PUBLIC_ONRAMPER_API_KEY }}
NEXT_PUBLIC_LAYERSWAP_API_KEY: ${{ secrets.PUBLIC_LAYERSWAP_API_KEY }}
- name: Install linux dependencies
run: |

View File

@@ -66,6 +66,7 @@ jobs:
NEXT_PUBLIC_LIFI_API_KEY=${{ env.NEXT_PUBLIC_LIFI_API_KEY }}
NEXT_PUBLIC_LIFI_INTEGRATOR_NAME=${{ env.NEXT_PUBLIC_LIFI_INTEGRATOR_NAME }}
NEXT_PUBLIC_ONRAMPER_API_KEY=${{ env.NEXT_PUBLIC_ONRAMPER_API_KEY }}
NEXT_PUBLIC_LAYERSWAP_API_KEY=${{ env.NEXT_PUBLIC_LAYERSWAP_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:
@@ -76,6 +77,7 @@ jobs:
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
NEXT_PUBLIC_LIFI_INTEGRATOR_NAME: ${{ secrets.PUBLIC_LIFI_INTEGRATOR_NAME }}
NEXT_PUBLIC_ONRAMPER_API_KEY: ${{ secrets.PUBLIC_ONRAMPER_API_KEY }}
NEXT_PUBLIC_LAYERSWAP_API_KEY: ${{ secrets.PUBLIC_LAYERSWAP_API_KEY }}
test-build:
if: github.event.pull_request.head.repo.fork == true

View File

@@ -27,6 +27,8 @@ NEXT_PUBLIC_QUICKNODE_ID=<GITHUB_SECRET>
NEXT_PUBLIC_LIFI_API_KEY=<GITHUB_SECRET>
NEXT_PUBLIC_LIFI_INTEGRATOR_NAME=<GITHUB_SECRET>
NEXT_PUBLIC_ONRAMPER_API_KEY=<GITHUB_SECRET>
NEXT_PUBLIC_LAYERSWAP_API_KEY=<GITHUB_SECRET>
NEXT_PUBLIC_IS_CCTP_ENABLED=true
E2E_TEST_PRIVATE_KEY=<GITHUB_SECRET>

View File

@@ -27,6 +27,8 @@ NEXT_PUBLIC_QUICKNODE_ID=<YOUR_QUICKNODE_ID>
NEXT_PUBLIC_LIFI_API_KEY=<LIFI_API_KEY>
NEXT_PUBLIC_LIFI_INTEGRATOR_NAME=<GITHUB_SECRET>
NEXT_PUBLIC_ONRAMPER_API_KEY=<ONRAMPER_API_KEY>
NEXT_PUBLIC_LAYERSWAP_API_KEY=<LAYERSWAP_API_KEY>
NEXT_PUBLIC_IS_CCTP_ENABLED=true
E2E_TEST_PRIVATE_KEY=<YOUR_PRIVATE_KEY>

View File

@@ -13,6 +13,7 @@ ARG NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID
ARG NEXT_PUBLIC_LIFI_API_KEY
ARG NEXT_PUBLIC_LIFI_INTEGRATOR_NAME
ARG NEXT_PUBLIC_ONRAMPER_API_KEY
ARG NEXT_PUBLIC_LAYERSWAP_API_KEY
ENV NEXT_PUBLIC_WALLET_CONNECT_ID=$NEXT_PUBLIC_WALLET_CONNECT_ID
ENV NEXT_PUBLIC_INFURA_ID=$NEXT_PUBLIC_INFURA_ID
@@ -21,6 +22,8 @@ ENV NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=$NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID
ENV NEXT_PUBLIC_LIFI_API_KEY=$NEXT_PUBLIC_LIFI_API_KEY
ENV NEXT_PUBLIC_LIFI_INTEGRATOR_NAME=$NEXT_PUBLIC_LIFI_INTEGRATOR_NAME
ENV NEXT_PUBLIC_ONRAMPER_API_KEY=$NEXT_PUBLIC_ONRAMPER_API_KEY
ENV NEXT_PUBLIC_LAYERSWAP_API_KEY=$NEXT_PUBLIC_LAYERSWAP_API_KEY
ARG ENV_FILE
WORKDIR /app

View File

@@ -1,3 +1,12 @@
<a name="v2.4.0"></a>
# [v2.4.0] - 09 May 2025
# Feat: Add Layerswap widget
Description:
- Add Layerswap widget
<a name="v2.3.0"></a>
# [v2.3.0] - 04 Apr 2025

View File

@@ -1,6 +1,6 @@
{
"name": "bridge-ui",
"version": "2.3.0",
"version": "2.4.0",
"private": true,
"type": "module",
"scripts": {
@@ -29,7 +29,7 @@
"@dynamic-labs/solana": "4.10.2",
"@dynamic-labs/wagmi-connector": "4.10.2",
"@headlessui/react": "2.1.9",
"@layerswap/widget": "^0.1.14",
"@layerswap/widget": "0.1.15",
"@lifi/widget": "3.18.2",
"@solana/wallet-adapter-base": "0.9.24",
"@solana/wallet-adapter-react": "0.15.36",
@@ -40,15 +40,12 @@
"auto-zustand-selectors-hook": "3.0.1",
"clsx": "2.1.1",
"date-fns": "4.1.0",
"framer-motion": "11.11.4",
"loglevel": "1.9.2",
"motion": "12.10.1",
"next": "14.2.25",
"next-seo": "6.6.0",
"pino-pretty": "13.0.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-icons": "5.5.0",
"sass": "1.86.0",
"sharp": "0.33.5",
"viem": "2.25.0",
"wagmi": "2.14.16",
@@ -66,6 +63,7 @@
"dotenv": "16.4.7",
"eslint-config-next": "14.2.15",
"nock": "14.0.1",
"postcss": "8.5.3"
"postcss": "8.5.3",
"sass": "1.86.0"
}
}

View File

@@ -0,0 +1,16 @@
"use client";
import FaqHelp from "@/components/bridge/faq-help";
import styles from "./page.module.scss";
import { Widget } from "@/components/lifi/widget";
export default function Page() {
return (
<>
<section className={styles["content-wrapper"]}>
<Widget />
</section>
<FaqHelp />
</>
);
}

View File

@@ -2,11 +2,15 @@
import OnRamperWidget from "@/components/onramper";
import styles from "./page.module.scss";
import FaqHelp from "@/components/bridge/faq-help";
export default function Page() {
return (
<section className={styles["content-wrapper"]}>
<OnRamperWidget />
</section>
<>
<section className={styles["content-wrapper"]}>
<OnRamperWidget />
</section>
<FaqHelp />
</>
);
}

View File

@@ -0,0 +1,18 @@
.content-wrapper {
max-width: 29.25rem;
margin: 0 auto;
width: calc(100% - 3rem);
@include bp("tablet") {
width: 100%;
}
}
.widget-wrapper {
font-family: var(--font-atyp-text);
@media (min-width: 640px) {
margin-bottom: -1.5rem;
}
}

View File

@@ -0,0 +1,16 @@
import { Widget } from "@/components/layerswap/widget";
import styles from "./page.module.scss";
import FaqHelp from "@/components/bridge/faq-help";
export default function Page() {
return (
<>
<section className={styles["content-wrapper"]}>
<div className={styles["widget-wrapper"]}>
<Widget />
</div>
</section>
<FaqHelp />
</>
);
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
import { motion } from "motion/react";
import styles from "./page.module.scss";
import Link from "next/link";

View File

@@ -1,10 +0,0 @@
import styles from "./page.module.scss";
import { Widget } from "@/components/layerswap/widget";
export default function Home() {
return (
<section className={styles["content-wrapper"]}>
<Widget />
</section>
);
}

View File

@@ -1,10 +1,26 @@
.content-wrapper {
max-width: 29.25rem;
max-width: 31.5rem;
margin: 0 auto;
width: calc(100% - 3rem);
background-color: var(--v2-color-white);
padding: 1.5rem;
border-radius: 0.625rem;
@include bp("tablet") {
width: 100%;
}
}
.title {
font-size: 1.5rem;
color: var(--v2-color-black);
line-height: 1.4;
margin-bottom: 1rem;
}
.cards-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}

View File

@@ -1,12 +1,22 @@
"use client";
import FaqHelp from "@/components/bridge/faq-help";
import styles from "./page.module.scss";
import { Widget } from "@/components/lifi/widget";
import { navList } from "@/components/internal-nav";
import NavItem from "@/components/internal-nav/item";
export default function Page() {
return (
<section className={styles["content-wrapper"]}>
<Widget />
</section>
<>
<section className={styles["content-wrapper"]}>
<h1 className={styles["title"]}>Fund Your Account</h1>
<ul className={styles["cards-list"]}>
{navList.map((item) => (
<NavItem key={item.href} {...item} />
))}
</ul>
</section>
<FaqHelp />
</>
);
}

View File

@@ -0,0 +1,13 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#6119EF" />
<g clip-path="url(#clip0_2826_166)">
<path
d="M23.2344 10.2461C23.4141 10.0859 23.6446 10 23.8829 10C24.5548 10 25.0235 10.668 24.7969 11.3008L22.1368 18.75H26.4454C27.0274 18.75 27.5001 19.2227 27.5001 19.8047C27.5001 20.1094 27.3712 20.3945 27.1446 20.5977L16.7618 29.7578C16.586 29.9141 16.3555 30 16.1173 30C15.4454 30 14.9766 29.332 15.1993 28.6992L17.8633 21.25H13.5079C12.9532 21.25 12.5001 20.7969 12.5001 20.2422C12.5001 19.9531 12.6251 19.6797 12.8399 19.4883L23.2344 10.2461ZM23.254 11.9023L14.1446 20H18.7501C18.9532 20 19.1446 20.0977 19.2618 20.2656C19.379 20.4336 19.4063 20.6445 19.3399 20.8359L16.7423 28.1094L25.9298 20H21.2501C21.0469 20 20.8555 19.9023 20.7383 19.7344C20.6212 19.5664 20.5938 19.3555 20.6602 19.1641L23.254 11.8984V11.9023Z"
fill="white" />
</g>
<defs>
<clipPath id="clip0_2826_166">
<rect width="17.5" height="20" fill="white" transform="translate(11 10)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,13 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#6119EF" />
<g clip-path="url(#clip0_2826_184)">
<path
d="M20.8749 10.625C20.8749 10.2812 20.5937 10 20.2499 10C19.9062 10 19.6249 10.2812 19.6249 10.625V12.4805C19.3789 12.4844 19.1328 12.4922 18.8906 12.5156C17.9531 12.5937 17.0195 12.8203 16.2578 13.3203C15.4726 13.832 14.9062 14.6094 14.6992 15.7031C14.5507 16.4961 14.6249 17.1953 14.9218 17.8008C15.2109 18.3984 15.6835 18.8477 16.2304 19.1953C17.2734 19.8594 18.7148 20.2383 20.0273 20.582L20.0937 20.5977C21.4882 20.9648 22.7421 21.2969 23.6015 21.8437C24.0156 22.1094 24.2968 22.3984 24.457 22.7266C24.6132 23.0508 24.6835 23.4687 24.5742 24.0469C24.4218 24.8555 23.8554 25.4687 22.8984 25.8437C21.9257 26.2266 20.5898 26.3359 19.0703 26.1172C18.1171 25.9766 16.6874 25.6445 15.5039 25.1328C15.1874 24.9961 14.8203 25.1406 14.6835 25.4609C14.5468 25.7812 14.6914 26.1445 15.0117 26.2812C16.3242 26.8477 17.8671 27.1992 18.8906 27.3516H18.8945C19.1445 27.3867 19.3906 27.4141 19.6328 27.4336V29.3672C19.6328 29.7109 19.914 29.9922 20.2578 29.9922C20.6015 29.9922 20.8828 29.7109 20.8828 29.3672V27.4687C21.789 27.4414 22.6328 27.2852 23.3593 27.0039C24.6093 26.5156 25.5546 25.6094 25.8085 24.2734C25.957 23.4805 25.8828 22.7812 25.5859 22.1758C25.2968 21.5781 24.8203 21.1289 24.2773 20.7852C23.2343 20.1211 21.7929 19.7422 20.4804 19.3984L20.414 19.3828C19.0195 19.0156 17.7656 18.6836 16.9062 18.1367C16.4921 17.8711 16.2109 17.582 16.0507 17.2539C15.8945 16.9297 15.8242 16.5117 15.9335 15.9336C16.0742 15.1797 16.4453 14.6914 16.9492 14.3633C17.4726 14.0195 18.1796 13.8281 19.0039 13.7578C20.6562 13.6211 22.5703 13.9922 23.8593 14.3047C24.1953 14.3867 24.5312 14.1797 24.6132 13.8437C24.6953 13.5078 24.4882 13.1719 24.1523 13.0898C23.2968 12.8828 22.1171 12.6328 20.8789 12.5273L20.8749 10.625Z"
fill="white" />
</g>
<defs>
<clipPath id="clip0_2826_184">
<rect width="12.5" height="20" fill="white" transform="translate(14 10)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,18 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#6119EF" />
<g clip-path="url(#clip0_2826_178)">
<g clip-path="url(#clip1_2826_178)">
<path
d="M20.3086 10.082C20.1172 9.97266 19.8828 9.97266 19.6914 10.082L10.6172 15.2227C10.2344 15.4414 10 15.8438 10 16.2812C10 16.9531 10.5469 17.5 11.2188 17.5H20H28.7812C29.4531 17.5 30 16.9531 30 16.2812C30 15.8438 29.7656 15.4375 29.3828 15.2227L20.3086 10.082ZM21.9766 16.25C22.1133 15.9648 22.1875 15.6484 22.1875 15.3125C22.1875 14.1055 21.207 13.125 20 13.125C18.793 13.125 17.8125 14.1055 17.8125 15.3125C17.8125 15.6484 17.8867 15.9648 18.0234 16.25H11.3398L20 11.3438L28.6602 16.25H21.9766ZM20 14.375C20.2486 14.375 20.4871 14.4738 20.6629 14.6496C20.8387 14.8254 20.9375 15.0639 20.9375 15.3125C20.9375 15.5611 20.8387 15.7996 20.6629 15.9754C20.4871 16.1512 20.2486 16.25 20 16.25C19.7514 16.25 19.5129 16.1512 19.3371 15.9754C19.1613 15.7996 19.0625 15.5611 19.0625 15.3125C19.0625 15.0639 19.1613 14.8254 19.3371 14.6496C19.5129 14.4738 19.7514 14.375 20 14.375ZM12.5 23.75C12.1562 23.75 11.875 24.0312 11.875 24.375C11.875 24.7188 12.1562 25 12.5 25H27.5C27.8438 25 28.125 24.7188 28.125 24.375C28.125 24.0312 27.8438 23.75 27.5 23.75V18.75H26.25V23.75H23.125V18.75H21.875V23.75H18.125V18.75H16.875V23.75H13.75V18.75H12.5V23.75ZM11.5625 26.25C11.2188 26.25 10.9375 26.5312 10.9375 26.875C10.9375 27.2188 11.2188 27.5 11.5625 27.5H28.4375C28.7812 27.5 29.0625 27.2188 29.0625 26.875C29.0625 26.5312 28.7812 26.25 28.4375 26.25H11.5625ZM10.625 28.75C10.2812 28.75 10 29.0312 10 29.375C10 29.7188 10.2812 30 10.625 30H29.375C29.7188 30 30 29.7188 30 29.375C30 29.0312 29.7188 28.75 29.375 28.75H10.625Z"
fill="white" />
</g>
</g>
<defs>
<clipPath id="clip0_2826_178">
<rect width="20" height="20" fill="white" transform="translate(10 10)" />
</clipPath>
<clipPath id="clip1_2826_178">
<rect width="20" height="20" fill="white" transform="translate(10 10)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,13 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#6119EF" />
<g clip-path="url(#clip0_2826_172)">
<path
d="M10.5556 13.087C10.25 13.087 10 13.3315 10 13.6305C10 13.9294 10.25 14.1739 10.5556 14.1739H12.7778V16.3478H10.5556C10.25 16.3478 10 16.5924 10 16.8913C10 17.1902 10.25 17.4348 10.5556 17.4348H13.3333H17.7778H22.2222H26.6667H29.4444C29.75 17.4348 30 17.1902 30 16.8913C30 16.5924 29.75 16.3478 29.4444 16.3478H27.2222V14.1739H29.4444C29.75 14.1739 30 13.9294 30 13.6305C30 13.3315 29.75 13.087 29.4444 13.087H26.6667H22.2222H17.7778H13.3333H10.5556ZM26.1111 14.1739V16.3478H22.7778V14.1739H26.1111ZM21.6667 14.1739V16.3478H18.3333V14.1739H21.6667ZM17.2222 14.1739V16.3478H13.8889V14.1739H17.2222ZM10.5556 20.1522C10.25 20.1522 10 20.3968 10 20.6957C10 20.9946 10.25 21.2391 10.5556 21.2391H11.1111C12.3368 21.2391 13.3333 22.214 13.3333 23.4131V26.6739C13.3333 27.5741 14.0799 28.3044 15 28.3044H16.1111C17.0313 28.3044 17.7778 27.5741 17.7778 26.6739V23.4131C17.7778 22.214 18.7743 21.2391 20 21.2391C21.2257 21.2391 22.2222 22.214 22.2222 23.4131V26.6739C22.2222 27.5741 22.9688 28.3044 23.8889 28.3044H25C25.9201 28.3044 26.6667 27.5741 26.6667 26.6739V23.4131C26.6667 22.214 27.6632 21.2391 28.8889 21.2391H29.4444C29.75 21.2391 30 20.9946 30 20.6957C30 20.3968 29.75 20.1522 29.4444 20.1522H28.8889C27.0486 20.1522 25.5556 21.6128 25.5556 23.4131V26.6739C25.5556 26.9728 25.3056 27.2174 25 27.2174H23.8889C23.5833 27.2174 23.3333 26.9728 23.3333 26.6739V23.4131C23.3333 21.6128 21.8403 20.1522 20 20.1522C18.1597 20.1522 16.6667 21.6128 16.6667 23.4131V26.6739C16.6667 26.9728 16.4167 27.2174 16.1111 27.2174H15C14.6944 27.2174 14.4444 26.9728 14.4444 26.6739V23.4131C14.4444 21.6128 12.9514 20.1522 11.1111 20.1522H10.5556Z"
fill="white" />
</g>
<defs>
<clipPath id="clip0_2826_172">
<rect width="20" height="20" fill="white" transform="translate(10 10)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -4,19 +4,6 @@
margin-top: 1.5rem;
font-family: var(--font-atyp-text);
&.is-mobile {
@include bp("tablet") {
display: none;
}
}
&.is-desktop {
display: none;
@include bp("tablet") {
display: block;
}
}
a {
color: var(--v2-color-indigo);
text-decoration: none;

View File

@@ -2,18 +2,9 @@ import Link from "next/link";
import styles from "./faq-help.module.scss";
import clsx from "clsx";
type Props = {
isMobile?: boolean;
};
export default function FaqHelp({ isMobile }: Props) {
export default function FaqHelp() {
return (
<div
className={clsx(styles["faq-help"], {
[styles["is-mobile"]]: isMobile,
[styles["is-desktop"]]: !isMobile,
})}
>
<div className={clsx(styles["faq-help"])}>
Need help? <Link href="/faq">Check our FAQ</Link>
</div>
);

View File

@@ -98,7 +98,6 @@ export default function BridgeForm() {
setIsDestinationAddressOpen={() => setIsDestinationAddressOpen((prev) => !prev)}
/>
</div>
<FaqHelp isMobile />
</div>
</div>
<FaqHelp />

View File

@@ -1,38 +1,111 @@
"use client";
import { usePathname } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { useDevice } from "@/hooks";
import clsx from "clsx";
import BridgeAggregatorIcon from "@/assets/icons/bridge-aggregator.svg";
import NativeBridgeIcon from "@/assets/icons/native-bridge.svg";
import BuyIcon from "@/assets/icons/buy.svg";
import CentralizedExchangeIcon from "@/assets/icons/centralized-exchange.svg";
import Modal from "@/components/modal";
import NavItem, { NavItemProps } from "./item";
import styles from "./internal-nav.module.scss";
import NavItem from "./item";
const NavData = [
export const navList: NavItemProps[] = [
{
title: "All Bridges",
href: "/",
},
{
title: "CEX",
href: "/layerswap",
title: "Bridge Aggregator",
description: "Bridge from any chain using the fastest route",
icon: <BridgeAggregatorIcon />,
label: "Fastest",
href: "/bridge-aggregator",
},
{
title: "Native Bridge",
description: "Bridge from Ethereum via Lineas official bridge",
icon: <NativeBridgeIcon />,
label: "No Fees",
href: "/native-bridge",
},
{
title: "Centralized Exchange",
description: "Transfer directly from any centralized exchange",
icon: <CentralizedExchangeIcon />,
href: "/centralized-exchange",
},
{
title: "Buy",
description: "Buy crypto with any payment methods",
icon: <BuyIcon />,
href: "/buy",
},
];
export default function InternalNav() {
const pathnane = usePathname();
export default function InternalNav({ hide }: { hide?: boolean }) {
const pathname = usePathname();
const { isMobile } = useDevice();
const [isOpen, setIsOpen] = useState(false);
const selected = useMemo(() => navList.find((item) => item.href === pathname), [pathname]);
const currentList = useMemo(() => navList.filter((item) => item.href !== pathname), [pathname]);
useEffect(() => {
if (isMobile) return;
const handleClickOutside = (event: MouseEvent) => {
const dropdownElement = document.querySelector(`.${styles["dropdown-wrapper"]}`);
if (dropdownElement && !dropdownElement.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [isMobile]);
useEffect(() => {
setIsOpen(false);
}, [pathname]);
const toggleDropdown = () => setIsOpen((prev) => !prev);
return (
<div className={styles["wrapper"]}>
<div className={styles["list-nav"]}>
{NavData.map((item, index) => (
<NavItem key={`internal-nav-item-${index}`} {...item} active={pathnane === item.href} />
))}
</div>
<div className={clsx(styles["wrapper"], { [styles["hide"]]: hide })}>
{!hide && (
<div className={styles["dropdown-wrapper"]}>
{selected && (
<button onClick={toggleDropdown}>
<NavItem {...selected} as="div" dropdown showCaret isOpen={isOpen} />
</button>
)}
{isMobile ? (
<Modal title="" isOpen={isOpen} isDrawer onClose={() => setIsOpen(false)}>
<div className={styles["modal-content"]}>
<ul className={styles["list-nav-mobile"]}>
{navList.map((item) => (
<NavItem key={`internal-nav-item-${item.href}`} {...item} dropdown />
))}
</ul>
</div>
</Modal>
) : (
<AnimatePresence>
{isOpen && (
<motion.ul
className={styles["list-nav"]}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
{currentList.map((item) => (
<NavItem key={`internal-nav-item-${item.href}`} {...item} dropdown />
))}
</motion.ul>
)}
</AnimatePresence>
)}
</div>
)}
</div>
);
}

View File

@@ -9,10 +9,37 @@
@include bp("tablet") {
margin: 3.75rem auto 1.5rem;
}
&.hide {
margin-bottom: 0;
}
}
.dropdown-wrapper {
position: relative;
width: 100%;
button {
width: 100%;
border-radius: 0.625rem;
overflow: hidden;
text-align: start;
}
}
.list-nav {
position: absolute;
top: 4.625rem;
z-index: 10;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
box-shadow: 0 0.25rem 0.25rem 0 rgba(18, 18, 18, 0.09);
background-color: var(--v2-color-white);
border-radius: 0.625rem;
overflow: hidden;
}
.list-nav-mobile {
padding: 0.5rem 0.5rem 1.5rem;
}

View File

@@ -1,22 +1,48 @@
import Link from "next/link";
import clsx from "clsx";
import ArrowRightIcon from "@/assets/icons/arrow-right.svg";
import CaretDownIcon from "@/assets/icons/caret-down.svg";
import styles from "./item.module.scss";
type Props = {
export type NavItemProps = {
title: string;
href: string;
active: boolean;
icon: React.ReactNode;
label?: string;
description: string;
};
export default function NavItem({ title, href, active }: Props) {
type Props = NavItemProps & {
as?: "div" | "li";
dropdown?: boolean;
showCaret?: boolean;
isOpen?: boolean;
};
export default function NavItem({ title, description, href, icon, label, as, dropdown, showCaret, isOpen }: Props) {
const Wrapper = as || "li";
return (
<Link
href={href}
className={clsx(styles["nav"], {
[styles["active"]]: active,
})}
>
<span>{title}</span>
</Link>
<Wrapper key={href} className={clsx(styles["card-item"], dropdown && styles["dropdown"], isOpen && styles["open"])}>
<Link href={href} className={styles["card-link"]}>
<div className={styles["card-wrapper"]}>
<span className={styles["card-icon"]}>{icon}</span>
<div className={styles["card-content"]}>
<div className={styles["card-title-wrapper"]}>
<h2>{title}</h2>
{label && <span className={styles["card-label"]}>{label}</span>}
</div>
<p className={styles["card-description"]}>{description}</p>
</div>
</div>
<span className={styles["right-arrow"]}>
<ArrowRightIcon />
</span>
{showCaret && (
<span className={styles["caret"]}>
<CaretDownIcon />
</span>
)}
</Link>
</Wrapper>
);
}

View File

@@ -1,18 +1,121 @@
.nav {
border-radius: 9999px;
display: flex;
align-items: center;
column-gap: 0.5rem;
padding: 0.75rem 1.375rem;
background-color: var(--v2-color-white);
text-decoration: none;
.card-item {
background-color: var(--v2-color-smoke);
border-radius: 0.625rem;
transition: background-color 0.25s ease-in-out;
&.active {
background-color: var(--v2-color-indigo);
color: var(--v2-color-white);
}
&:hover:not(.active) {
background-color: var(--v2-color-icterine);
&.dropdown {
background-color: var(--v2-color-white);
border-radius: unset;
width: 100%;
&.open {
.caret {
transform: rotate(180deg);
}
}
&:hover {
background-color: var(--v2-color-light-pink);
}
.card-link {
padding: 0.75rem 1rem;
}
.right-arrow {
display: none;
}
}
}
.card-link {
text-decoration: none;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
&:hover {
.right-arrow {
background-color: var(--v2-color-cyan);
}
}
}
.card-wrapper {
display: flex;
align-items: center;
gap: 0.625rem;
}
.card-icon {
flex-shrink: 0;
width: 2.5rem;
height: 2.5rem;
align-self: flex-start;
}
.card-content {
color: var(--v2-color-navy);
font-family: var(--font-atyp-text);
display: flex;
flex-direction: column;
justify-content: space-between;
align-self: stretch;
.card-title {
font-size: 1rem;
line-height: 1.4;
}
.card-description {
font-size: 0.75rem;
line-height: 1.4;
}
}
.card-title-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-label {
background-color: var(--v2-color-light-pink);
color: var(--v2-color-indigo);
font-size: 0.75rem;
line-height: 1;
padding: 0.25rem 0.5rem;
border-radius: 3.125rem;
}
.right-arrow {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2rem;
background-color: var(--v2-color-white);
border-radius: 50%;
transition: background-color 0.25s ease-in-out;
svg {
width: 0.875rem;
height: 0.875rem;
}
}
.caret {
display: flex;
align-items: center;
justify-content: center;
color: var(--v2-color-darker-gray);
transition: transform 0.25s ease-in-out;
svg {
width: 1.125rem;
height: 1.125rem;
}
}

View File

@@ -0,0 +1,31 @@
"use client";
import { config } from "@/config";
import styles from "./layerswap.module.scss";
const layerswapConfig: Record<string, string | string[]> = {
clientId: config.layerswapApiKey,
};
function generateLayerswapUrl(config: Record<string, string | string[]>): string {
const url = new URL("https://layerswap.io/app/");
const params = new URLSearchParams();
for (const [key, value] of Object.entries(config)) {
const val = Array.isArray(value) ? value.join(",") : value;
params.set(key, val);
}
url.search = params.toString();
return url.toString();
}
export default function LayerSwapWidget() {
return (
<iframe
className={styles["layerswap-iframe"]}
src={generateLayerswapUrl(layerswapConfig)}
title="Linea LayerSwap Widget"
/>
);
}

View File

@@ -0,0 +1,5 @@
.layerswap-iframe {
width: 100%;
height: 600px;
border-radius: 0.625rem;
}

View File

@@ -1,11 +1,18 @@
import "@layerswap/widget/index.css";
import { Swap, LayerswapProvider, GetSettings, ThemeData } from "@layerswap/widget";
import CustomHooks from "./custom-hooks";
import { config } from "@/config";
export async function Widget() {
const settings = await GetSettings();
return (
<LayerswapProvider integrator="linea" themeData={themeData} settings={settings} apiKey={"YOUR_API_KEY"}>
<LayerswapProvider
integrator="linea"
themeData={themeData}
settings={settings}
apiKey={config.layerswapApiKey}
version="mainnet"
>
<CustomHooks>
<Swap
featuredNetwork={{
@@ -18,11 +25,11 @@ export async function Widget() {
</LayerswapProvider>
);
}
// TODO: Implement theme colors
const themeData: ThemeData = {
placeholderText: "134, 134, 134",
placeholderText: "82, 82, 82",
actionButtonText: "255, 255, 255",
buttonTextColor: "17, 17, 17",
buttonTextColor: "18, 18, 18",
logo: "255, 0, 147",
borderRadius: "large",
primary: {
@@ -50,9 +57,9 @@ const themeData: ThemeData = {
"500": "228, 227, 219",
"600": "240, 240, 235",
"700": "248, 247, 241",
"800": "243, 244, 246",
"800": "255, 255, 255",
"900": "255, 255, 255",
"950": "255, 255, 255",
text: "82, 82, 82",
text: "18, 18, 18",
},
};

View File

@@ -17,67 +17,30 @@ export function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
if (!sdkHasLoaded) {
return (
<div className="layout">
<div className="container-v2">
<Header theme={Theme.navy} />
<main>
<div className={styles["content-wrapper"]}>
<InternalNav />
</div>
{children}
</main>
</div>
<div>
<Image
className="left-illustration"
src={"/images/illustration/illustration-left.svg"}
role="presentation"
alt="illustration left"
width={300}
height={445}
priority
/>
<Image
className="right-illustration"
src={"/images/illustration/illustration-right.svg"}
role="presentation"
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={0}
height={0}
style={{ width: "100%", height: "auto", objectFit: "cover" }}
priority
/>
</div>
</div>
);
return <CommonLayout pathname={pathname}>{children}</CommonLayout>;
}
return <CommonLayout pathname={pathname}>{children}</CommonLayout>;
}
function CommonLayout({ children, pathname }: { children: React.ReactNode; pathname: string }) {
return (
<div className="layout">
<div className="container-v2">
<Header theme={Theme.navy} />
<main>
<div className={styles["content-wrapper"]}>
<InternalNav />
</div>
{pathname !== "/faq" && (
<div className={styles["content-wrapper"]}>
<InternalNav hide={pathname === "/"} />
</div>
)}
{children}
</main>
</div>
<div>
<Image
className="left-illustration"
src={"/images/illustration/illustration-left.svg"}
src="/images/illustration/illustration-left.svg"
role="presentation"
alt="illustration left"
width={300}
@@ -86,7 +49,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
/>
<Image
className="right-illustration"
src={"/images/illustration/illustration-right.svg"}
src="/images/illustration/illustration-right.svg"
role="presentation"
alt="illustration right"
width={610}
@@ -95,7 +58,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
/>
<Image
className={clsx("mobile-illustration", { hidden: pathname === "/faq" })}
src={"/images/illustration/illustration-mobile.svg"}
src="/images/illustration/illustration-mobile.svg"
role="presentation"
alt="illustration mobile"
width={0}

View File

@@ -12,7 +12,7 @@ import CloseIcon from "@/assets/icons/close.svg";
export type VisitedModalType = "all-bridges" | "native-bridge" | "buy";
const pathToModalType: Record<string, VisitedModalType> = {
"/": "all-bridges",
"/bridge-aggregator": "all-bridges",
"/native-bridge": "native-bridge",
"/buy": "buy",
};

View File

@@ -1,8 +1,8 @@
import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "framer-motion";
import { motion, AnimatePresence } from "motion/react";
import CloseIcon from "@/assets/icons/close.svg";
import styles from "./modal.module.scss";
import { useEffect, useState } from "react";
import { JSX, useEffect, useState } from "react";
import clsx from "clsx";
type Props = {

View File

@@ -35,6 +35,7 @@ export const configSchema = z
lifiApiKey: z.string().nonempty(),
lifiIntegrator: z.string().nonempty(),
onRamperApiKey: z.string().nonempty(),
layerswapApiKey: z.string().nonempty(),
tokenListUrls: z.object({
mainnet: z.string().trim().url(),
sepolia: z.string().trim().url(),

View File

@@ -72,6 +72,7 @@ export const config: Config = {
lifiApiKey: process.env.NEXT_PUBLIC_LIFI_API_KEY ?? "",
lifiIntegrator: process.env.NEXT_PUBLIC_LIFI_INTEGRATOR_NAME ?? "",
onRamperApiKey: process.env.NEXT_PUBLIC_ONRAMPER_API_KEY ?? "",
layerswapApiKey: process.env.NEXT_PUBLIC_LAYERSWAP_API_KEY ?? "",
tokenListUrls: {
mainnet: process.env.NEXT_PUBLIC_MAINNET_TOKEN_LIST ?? "",
sepolia: process.env.NEXT_PUBLIC_SEPOLIA_TOKEN_LIST ?? "",

View File

@@ -75,7 +75,7 @@ export function Web3Provider({ children }: Web3ProviderProps) {
cssOverrides,
}}
>
<WagmiProvider config={wagmiConfig} reconnectOnMount={false}>
<WagmiProvider config={wagmiConfig}>
<DynamicWagmiConnector>
<SolanaWalletProvider>{children}</SolanaWalletProvider>
</DynamicWagmiConnector>

115
pnpm-lock.yaml generated
View File

@@ -63,8 +63,8 @@ importers:
specifier: 2.1.9
version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@layerswap/widget':
specifier: ^0.1.14
version: 0.1.14(36cdbs6sbt4hnf6malkxxidv2e)
specifier: 0.1.15
version: 0.1.15(36cdbs6sbt4hnf6malkxxidv2e)
'@lifi/widget':
specifier: 3.18.2
version: 3.18.2(6llx6mg3jnie75ujog4w6rkshe)
@@ -95,18 +95,15 @@ importers:
date-fns:
specifier: 4.1.0
version: 4.1.0
framer-motion:
specifier: 11.11.4
version: 11.11.4(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
loglevel:
specifier: 1.9.2
version: 1.9.2
motion:
specifier: 12.10.1
version: 12.10.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next:
specifier: 14.2.25
version: 14.2.25(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0)
next-seo:
specifier: 6.6.0
version: 6.6.0(next@14.2.25(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
pino-pretty:
specifier: 13.0.0
version: 13.0.0
@@ -116,12 +113,6 @@ importers:
react-dom:
specifier: 18.3.1
version: 18.3.1(react@18.3.1)
react-icons:
specifier: 5.5.0
version: 5.5.0(react@18.3.1)
sass:
specifier: 1.86.0
version: 1.86.0
sharp:
specifier: 0.33.5
version: 0.33.5
@@ -171,6 +162,9 @@ importers:
postcss:
specifier: 8.5.3
version: 8.5.3
sass:
specifier: 1.86.0
version: 1.86.0
contracts:
dependencies:
@@ -294,10 +288,10 @@ importers:
version: 6.13.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)
jest:
specifier: 29.7.0
version: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))
version: 29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))
ts-jest:
specifier: 29.2.5
version: 29.2.5(@babel/core@7.25.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)))(typescript@5.4.5)
version: 29.2.5(@babel/core@7.25.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5)
typechain:
specifier: 8.3.2
version: 8.3.2(typescript@5.4.5)
@@ -334,16 +328,16 @@ importers:
version: 29.5.13
jest:
specifier: 29.7.0
version: 29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))
version: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))
jest-mock-extended:
specifier: 3.0.5
version: 3.0.5(jest@29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5)
version: 3.0.5(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)))(typescript@5.4.5)
shx:
specifier: 0.3.4
version: 0.3.4
ts-jest:
specifier: 29.2.5
version: 29.2.5(@babel/core@7.25.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5)
version: 29.2.5(@babel/core@7.25.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)))(typescript@5.4.5)
postman:
dependencies:
@@ -2210,8 +2204,8 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@layerswap/widget@0.1.14':
resolution: {integrity: sha512-+h2rLGhzSzptWb2mNE1J2Iygl3uLnEylH+cwW0CLJx93axlLQtMK2It2pAof5bM6b4Ni5d4r98tYvhN/1mcM4A==}
'@layerswap/widget@0.1.15':
resolution: {integrity: sha512-4AS7XS59AFfdm4QbGxXR3F9WqPE4a5KSxofqiMy8Jo5iJlkQH/4D+FSoPET1OFofy6KSl4naejIA6m0x1r6GWA==}
engines: {node: '>=22'}
peerDependencies:
'@tanstack/react-query': ^5.59.20
@@ -6681,12 +6675,12 @@ packages:
react-dom:
optional: true
framer-motion@11.11.4:
resolution: {integrity: sha512-54UE9loF2ZBgyjdMmxzp9kxm0gvYjFfpd7GiThdCCp5+J0P4Oa7pM0yusd9Y3rm/EUAdD+FfgqRXfbK7k3RFiA==}
framer-motion@12.10.1:
resolution: {integrity: sha512-g+fANUVC17SzQc6eA0CtomBW4n67ckhS2hq5fjkKZneKzv7sbdXK3zzjnnAKB22Ck+Qhh+IlO5RjHNKULsq99Q==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0
react-dom: ^18.0.0
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
@@ -8323,9 +8317,29 @@ packages:
mock-fs@4.14.0:
resolution: {integrity: sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==}
motion-dom@12.10.1:
resolution: {integrity: sha512-rY8DNqgKh4LeFSQBkuXpe/7sycYS9RM+4luukjHpHogF1liSvIp0Hedx0q2QsWNz+AHuZ5bZQ9j9QZSUCA8bbw==}
motion-utils@12.9.4:
resolution: {integrity: sha512-BW3I65zeM76CMsfh3kHid9ansEJk9Qvl+K5cu4DVHKGsI52n76OJ4z2CUJUV+Mn3uEP9k1JJA3tClG0ggSrRcg==}
motion@10.16.2:
resolution: {integrity: sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==}
motion@12.10.1:
resolution: {integrity: sha512-WBpo3oiIQRDMQqMRn3DAgBJrjqGMKY+BomsINn67ot8c+691pwHvUUo+kvknck7kNkUPOAq2EHbAEwlx+5Vfow==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -8392,13 +8406,6 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next-seo@6.6.0:
resolution: {integrity: sha512-0VSted/W6XNtgAtH3D+BZrMLLudqfm0D5DYNJRXHcDgan/1ZF1tDFIsWrmvQlYngALyphPfZ3ZdOqlKpKdvG6w==}
peerDependencies:
next: ^8.1.1-canary.54 || >=9.0.0
react: '>=16.0.0'
react-dom: '>=16.0.0'
next-tick@1.1.0:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
@@ -9361,11 +9368,6 @@ packages:
react-native:
optional: true
react-icons@5.5.0:
resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
peerDependencies:
react: '*'
react-international-phone@4.2.5:
resolution: {integrity: sha512-jXxeEG5jvwivwSb/ImIIwIH1lSGD6VSy4W2CaInBiXo2PWnDj2BTzC0sAyZzNJarT7NX9kPdUHyGyyfziS5Rpw==}
peerDependencies:
@@ -13973,7 +13975,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@layerswap/widget@0.1.14(36cdbs6sbt4hnf6malkxxidv2e)':
'@layerswap/widget@0.1.15(36cdbs6sbt4hnf6malkxxidv2e)':
dependencies:
'@headlessui/react': 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@metamask/jazzicon': 2.0.0
@@ -19697,7 +19699,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.7(supports-color@8.1.1)
debug: 4.4.0(supports-color@8.1.1)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -20342,8 +20344,10 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
framer-motion@11.11.4(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
framer-motion@12.10.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
motion-dom: 12.10.1
motion-utils: 12.9.4
tslib: 2.7.0
optionalDependencies:
'@emotion/is-prop-valid': 1.3.1
@@ -21594,12 +21598,6 @@ snapshots:
slash: 3.0.0
stack-utils: 2.0.6
jest-mock-extended@3.0.5(jest@29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5):
dependencies:
jest: 29.7.0(@types/node@20.12.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))
ts-essentials: 7.0.3(typescript@5.4.5)
typescript: 5.4.5
jest-mock-extended@3.0.5(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)))(typescript@5.4.5):
dependencies:
jest: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))
@@ -22513,6 +22511,12 @@ snapshots:
mock-fs@4.14.0: {}
motion-dom@12.10.1:
dependencies:
motion-utils: 12.9.4
motion-utils@12.9.4: {}
motion@10.16.2:
dependencies:
'@motionone/animation': 10.18.0
@@ -22522,6 +22526,15 @@ snapshots:
'@motionone/utils': 10.18.0
'@motionone/vue': 10.16.4
motion@12.10.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
framer-motion: 12.10.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
tslib: 2.7.0
optionalDependencies:
'@emotion/is-prop-valid': 1.3.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
mri@1.2.0: {}
ms@2.0.0: {}
@@ -22583,12 +22596,6 @@ snapshots:
neo-async@2.6.2: {}
next-seo@6.6.0(next@14.2.25(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 14.2.25(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next-tick@1.1.0: {}
next@14.2.25(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.86.0):
@@ -23500,10 +23507,6 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
react-native: 0.74.0(@babel/core@7.25.7)(@babel/preset-env@7.25.7(@babel/core@7.25.7))(@types/react@18.3.11)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10)
react-icons@5.5.0(react@18.3.1):
dependencies:
react: 18.3.1
react-international-phone@4.2.5(react@18.3.1):
dependencies:
react: 18.3.1