mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-08 23:17:58 -05:00
Layerswap widget integration (#929)
* Add layerswap widget * update @layerswap/widget version to 0.1.11 * fix: update network logo references from 'ethereum' to 'linea' * update @layerswap/widget to 0.1.14 * Refactored evm wallet hook * Renamed nav * update the pnpm-lock.yml * fix: lint code --------- Co-authored-by: Aren <aren@bransfer.io> Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Co-authored-by: VGau <victorien.gauch@consensys.net>
This commit is contained in:
@@ -29,6 +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",
|
||||
"@lifi/widget": "3.18.2",
|
||||
"@solana/wallet-adapter-base": "0.9.24",
|
||||
"@solana/wallet-adapter-react": "0.15.36",
|
||||
|
||||
10
bridge-ui/src/app/layerswap/page.module.scss
Normal file
10
bridge-ui/src/app/layerswap/page.module.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.content-wrapper {
|
||||
max-width: 29.25rem;
|
||||
margin: 0 auto;
|
||||
|
||||
width: calc(100% - 3rem);
|
||||
|
||||
@include bp("tablet") {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
10
bridge-ui/src/app/layerswap/page.tsx
Normal file
10
bridge-ui/src/app/layerswap/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styles from "./page.module.scss";
|
||||
import { Widget } from "@/components/layerswap/widget";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<section className={styles["content-wrapper"]}>
|
||||
<Widget />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,10 @@ const NavData = [
|
||||
title: "All Bridges",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
title: "CEX",
|
||||
href: "/layerswap",
|
||||
},
|
||||
{
|
||||
title: "Native Bridge",
|
||||
href: "/native-bridge",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
import { WalletHooksProvider } from "@layerswap/widget";
|
||||
import useCustomEVM from "./useCustomEvm";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function CustomHooks({ children }: { children: ReactNode }) {
|
||||
const customEvm = useCustomEVM();
|
||||
return <WalletHooksProvider overides={{ evm: customEvm }}>{children}</WalletHooksProvider>;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { useAccount, useSwitchAccount } from "wagmi";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import {
|
||||
useUserWallets,
|
||||
useDynamicContext,
|
||||
dynamicEvents,
|
||||
Wallet as DynamicWallet,
|
||||
} from "@dynamic-labs/sdk-react-core";
|
||||
import {
|
||||
WalletProvider,
|
||||
Wallet,
|
||||
resolveWalletConnectorIcon,
|
||||
useSettingsState,
|
||||
NetworkWithTokens,
|
||||
NetworkType,
|
||||
} from "@layerswap/widget";
|
||||
|
||||
export default function useEVM(): WalletProvider {
|
||||
const name = "EVM";
|
||||
const id = "evm";
|
||||
|
||||
// wagmi
|
||||
const { connectors: activeConnectors } = useSwitchAccount();
|
||||
const { connector: activeConnector, address: activeAddress } = useAccount();
|
||||
// Dynamic SDK
|
||||
const { setShowAuthFlow, handleLogOut } = useDynamicContext();
|
||||
const userWallets = useUserWallets();
|
||||
// Layerswap settings
|
||||
const { networks } = useSettingsState();
|
||||
|
||||
// Gather the EVM‐type network names
|
||||
const evmNetworkNames = useMemo(
|
||||
() => networks.filter((n) => n.type === NetworkType.EVM).map((n) => n.name),
|
||||
[networks],
|
||||
);
|
||||
|
||||
// Supported-networks
|
||||
const supportedNetworks = useMemo(
|
||||
() => ({
|
||||
asSource: evmNetworkNames,
|
||||
autofill: evmNetworkNames,
|
||||
withdrawal: evmNetworkNames,
|
||||
}),
|
||||
[evmNetworkNames],
|
||||
);
|
||||
|
||||
// Clean up dynamicEvents listeners on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dynamicEvents.removeAllListeners("walletAdded");
|
||||
dynamicEvents.removeAllListeners("authFlowCancelled");
|
||||
};
|
||||
}, []);
|
||||
|
||||
// connectWallet: log out existing, show authFlow, wait for event, then resolve
|
||||
const connectWallet = useCallback(async (): Promise<Wallet | undefined> => {
|
||||
if (userWallets.length) {
|
||||
await handleLogOut();
|
||||
}
|
||||
|
||||
const newDynWallet = await new Promise<DynamicWallet>((resolve, reject) => {
|
||||
setShowAuthFlow(true);
|
||||
|
||||
const onAdded = (w: DynamicWallet) => {
|
||||
cleanup();
|
||||
resolve(w);
|
||||
};
|
||||
const onCancelled = () => {
|
||||
cleanup();
|
||||
reject(new Error("User cancelled the connection"));
|
||||
};
|
||||
const cleanup = () => {
|
||||
dynamicEvents.off("walletAdded", onAdded);
|
||||
dynamicEvents.off("authFlowCancelled", onCancelled);
|
||||
};
|
||||
|
||||
dynamicEvents.on("walletAdded", onAdded);
|
||||
dynamicEvents.on("authFlowCancelled", onCancelled);
|
||||
});
|
||||
|
||||
return resolveWallet({
|
||||
connection: newDynWallet,
|
||||
activeConnection:
|
||||
activeConnector && activeAddress ? { id: activeConnector.id, address: activeAddress } : undefined,
|
||||
networks,
|
||||
supportedNetworks,
|
||||
disconnect: handleLogOut,
|
||||
providerName: name,
|
||||
});
|
||||
}, [userWallets, handleLogOut, setShowAuthFlow, activeConnector, activeAddress, networks, supportedNetworks]);
|
||||
|
||||
// Logout
|
||||
const disconnectWallets = useCallback(async () => {
|
||||
await handleLogOut();
|
||||
}, [handleLogOut]);
|
||||
|
||||
// Map wagmi connectors → Dynamic SDK wallets → our Wallet shape
|
||||
const connectedWallets: Wallet[] = useMemo(
|
||||
() =>
|
||||
activeConnectors
|
||||
.map((conn) => {
|
||||
const dyn = userWallets.find((w) => true);
|
||||
if (!dyn) return;
|
||||
return resolveWallet({
|
||||
connection: dyn,
|
||||
activeConnection:
|
||||
activeConnector && activeAddress ? { id: activeConnector.id, address: activeAddress } : undefined,
|
||||
networks,
|
||||
supportedNetworks,
|
||||
disconnect: disconnectWallets,
|
||||
providerName: name,
|
||||
});
|
||||
})
|
||||
.filter(Boolean) as Wallet[],
|
||||
[activeConnectors, userWallets, activeConnector, activeAddress, networks, supportedNetworks, disconnectWallets],
|
||||
);
|
||||
|
||||
const logo = networks.find((n) => n.name.toLowerCase().includes("linea"))?.logo;
|
||||
|
||||
return {
|
||||
connectWallet,
|
||||
connectConnector: connectWallet,
|
||||
activeWallet: connectedWallets.find((w) => w.isActive),
|
||||
connectedWallets,
|
||||
asSourceSupportedNetworks: supportedNetworks.asSource,
|
||||
autofillSupportedNetworks: supportedNetworks.autofill,
|
||||
withdrawalSupportedNetworks: supportedNetworks.withdrawal,
|
||||
name,
|
||||
id,
|
||||
providerIcon: logo,
|
||||
};
|
||||
}
|
||||
|
||||
/** Reusable helper to turn a DynamicWallet + context into our `Wallet` shape */
|
||||
function resolveWallet(props: {
|
||||
connection: DynamicWallet;
|
||||
activeConnection?: { id: string; address: string };
|
||||
networks: NetworkWithTokens[];
|
||||
supportedNetworks: {
|
||||
asSource: string[];
|
||||
autofill: string[];
|
||||
withdrawal: string[];
|
||||
};
|
||||
disconnect: () => Promise<void>;
|
||||
providerName: string;
|
||||
}): Wallet | undefined {
|
||||
const { connection, activeConnection, networks, supportedNetworks, disconnect, providerName } = props;
|
||||
|
||||
const connectorName = connection.connector.name;
|
||||
const address = connection.address;
|
||||
if (!connectorName || !address) return;
|
||||
|
||||
const isActive = activeConnection?.address === address;
|
||||
const displayName = `${connectorName} – ${providerName}`;
|
||||
const networkIcon = networks.find((n) => n.name.toLowerCase().includes("linea"))?.logo;
|
||||
|
||||
return {
|
||||
id: connectorName,
|
||||
isActive,
|
||||
address,
|
||||
addresses: [address],
|
||||
displayName,
|
||||
providerName,
|
||||
icon: resolveWalletConnectorIcon({ connector: connectorName, address }),
|
||||
disconnect: () => disconnect(),
|
||||
asSourceSupportedNetworks: supportedNetworks.asSource,
|
||||
autofillSupportedNetworks: supportedNetworks.autofill,
|
||||
withdrawalSupportedNetworks: supportedNetworks.withdrawal,
|
||||
networkIcon,
|
||||
};
|
||||
}
|
||||
58
bridge-ui/src/components/layerswap/widget/index.tsx
Normal file
58
bridge-ui/src/components/layerswap/widget/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import "@layerswap/widget/index.css";
|
||||
import { Swap, LayerswapProvider, GetSettings, ThemeData } from "@layerswap/widget";
|
||||
import CustomHooks from "./custom-hooks";
|
||||
|
||||
export async function Widget() {
|
||||
const settings = await GetSettings();
|
||||
return (
|
||||
<LayerswapProvider integrator="linea" themeData={themeData} settings={settings} apiKey={"YOUR_API_KEY"}>
|
||||
<CustomHooks>
|
||||
<Swap
|
||||
featuredNetwork={{
|
||||
initialDirection: "to",
|
||||
network: "LINEA_MAINNET",
|
||||
oppositeDirectionOverrides: "onlyExchanges",
|
||||
}}
|
||||
/>
|
||||
</CustomHooks>
|
||||
</LayerswapProvider>
|
||||
);
|
||||
}
|
||||
// TODO: Implement theme colors
|
||||
const themeData: ThemeData = {
|
||||
placeholderText: "134, 134, 134",
|
||||
actionButtonText: "255, 255, 255",
|
||||
buttonTextColor: "17, 17, 17",
|
||||
logo: "255, 0, 147",
|
||||
borderRadius: "large",
|
||||
primary: {
|
||||
DEFAULT: "97, 26, 239",
|
||||
"50": "215, 198, 251",
|
||||
"100": "202, 179, 250",
|
||||
"200": "176, 140, 247",
|
||||
"300": "150, 102, 244",
|
||||
"400": "123, 64, 242",
|
||||
"500": "97, 26, 239",
|
||||
"600": "74, 14, 195",
|
||||
"700": "54, 10, 143",
|
||||
"800": "34, 6, 90",
|
||||
"900": "14, 3, 38",
|
||||
text: "18, 18, 18",
|
||||
textMuted: "86, 97, 123",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "248, 247, 241",
|
||||
"50": "49, 60, 155",
|
||||
"100": "46, 59, 147",
|
||||
"200": "134, 134, 134",
|
||||
"300": "139, 139, 139",
|
||||
"400": "220, 219, 214",
|
||||
"500": "228, 227, 219",
|
||||
"600": "240, 240, 235",
|
||||
"700": "248, 247, 241",
|
||||
"800": "243, 244, 246",
|
||||
"900": "255, 255, 255",
|
||||
"950": "255, 255, 255",
|
||||
text: "82, 82, 82",
|
||||
},
|
||||
};
|
||||
1660
pnpm-lock.yaml
generated
1660
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user