mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 15:38:06 -05:00
Feat: Add Solana support in the bridge UI (#836)
* feat: add solona support in the LiFi widget * fix: update dockerfile and update bridge ui version * fix: handle cases where user is connected to non EVM network in native bridge * fix: bridge ui e2e tests issue
This commit is contained in:
2
.github/workflows/bridge-ui-e2e-tests.yml
vendored
2
.github/workflows/bridge-ui-e2e-tests.yml
vendored
@@ -55,6 +55,7 @@ jobs:
|
||||
env:
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
|
||||
NEXT_PUBLIC_ALCHEMY_ID: ${{ secrets.PUBLIC_BRIDGE_UI_ALCHEMY_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_SANDBOX_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
|
||||
NEXT_PUBLIC_ONRAMPER_API_KEY: ${{ secrets.PUBLIC_ONRAMPER_API_KEY }}
|
||||
@@ -80,6 +81,7 @@ jobs:
|
||||
NEXT_PUBLIC_LIFI_API_KEY: "placeholder"
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID: "placeholder"
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: "placeholder"
|
||||
NEXT_PUBLIC_ALCHEMY_ID: "placeholder"
|
||||
|
||||
# Prerequisite - Testing wallet must have >0 ETH and USDC on Sepolia
|
||||
- name: Run E2E tests
|
||||
|
||||
2
.github/workflows/bridge-ui-publish.yml
vendored
2
.github/workflows/bridge-ui-publish.yml
vendored
@@ -60,6 +60,7 @@ jobs:
|
||||
ENV_FILE=./bridge-ui/.env.production
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID=${{ env.NEXT_PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID=${{ env.NEXT_PUBLIC_INFURA_ID }}
|
||||
NEXT_PUBLIC_ALCHEMY_ID=${{ env.NEXT_PUBLIC_ALCHEMY_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=${{ env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY=${{ env.NEXT_PUBLIC_LIFI_API_KEY }}
|
||||
NEXT_PUBLIC_ONRAMPER_API_KEY=${{ env.NEXT_PUBLIC_ONRAMPER_API_KEY }}
|
||||
@@ -68,6 +69,7 @@ jobs:
|
||||
env:
|
||||
NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_ID }}
|
||||
NEXT_PUBLIC_INFURA_ID: ${{ secrets.PUBLIC_BRIDGE_UI_INFURA_ID }}
|
||||
NEXT_PUBLIC_ALCHEMY_ID: ${{ secrets.PUBLIC_BRIDGE_UI_ALCHEMY_ID }}
|
||||
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID: ${{ secrets.PUBLIC_DYNAMIC_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_LIFI_API_KEY: ${{ secrets.PUBLIC_LIFI_API_KEY }}
|
||||
NEXT_PUBLIC_ONRAMPER_API_KEY: ${{ secrets.PUBLIC_ONRAMPER_API_KEY }}
|
||||
|
||||
@@ -8,11 +8,14 @@ FROM base AS builder
|
||||
|
||||
ARG NEXT_PUBLIC_WALLET_CONNECT_ID
|
||||
ARG NEXT_PUBLIC_INFURA_ID
|
||||
ARG NEXT_PUBLIC_ALCHEMY_ID
|
||||
ARG NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID
|
||||
ARG NEXT_PUBLIC_LIFI_API_KEY
|
||||
ARG NEXT_PUBLIC_ONRAMPER_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_ALCHEMY_ID=$NEXT_PUBLIC_ALCHEMY_ID
|
||||
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_ONRAMPER_API_KEY=$NEXT_PUBLIC_ONRAMPER_API_KEY
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<a name="v2.3.0"></a>
|
||||
|
||||
# [v2.3.0] - 04 Apr 2025
|
||||
|
||||
# Feat: Add Solana support in the LiFi widget
|
||||
|
||||
Description:
|
||||
- Add Solana support in the LiFi widget
|
||||
|
||||
<a name="v2.2.0"></a>
|
||||
|
||||
# [v2.2.0] - 31 Mar 2025
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bridge-ui",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -24,13 +24,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@consensys/linea-sdk": "0.3.0",
|
||||
"@dynamic-labs/ethereum": "4.9.11",
|
||||
"@dynamic-labs/sdk-react-core": "4.9.11",
|
||||
"@dynamic-labs/wagmi-connector": "4.9.11",
|
||||
"@dynamic-labs/ethereum": "4.10.2",
|
||||
"@dynamic-labs/sdk-react-core": "4.10.2",
|
||||
"@dynamic-labs/solana": "4.10.2",
|
||||
"@dynamic-labs/wagmi-connector": "4.10.2",
|
||||
"@headlessui/react": "2.1.9",
|
||||
"@lifi/widget": "3.18.1",
|
||||
"@tanstack/react-query": "5.69.0",
|
||||
"@wagmi/connectors": "5.7.11",
|
||||
"@lifi/widget": "3.18.2",
|
||||
"@solana/wallet-adapter-base": "0.9.24",
|
||||
"@solana/wallet-adapter-react": "0.15.36",
|
||||
"@solana/web3.js": "1.98.0",
|
||||
"@tanstack/react-query": "5.71.5",
|
||||
"@wagmi/connectors": "5.7.12",
|
||||
"@wagmi/core": "2.16.7",
|
||||
"auto-zustand-selectors-hook": "3.0.1",
|
||||
"clsx": "2.1.1",
|
||||
@@ -45,8 +49,8 @@
|
||||
"react-icons": "5.5.0",
|
||||
"sass": "1.86.0",
|
||||
"sharp": "0.33.5",
|
||||
"viem": "2.23.13",
|
||||
"wagmi": "2.14.15",
|
||||
"viem": "2.25.0",
|
||||
"wagmi": "2.14.16",
|
||||
"zod": "3.24.2",
|
||||
"zustand": "4.5.4"
|
||||
},
|
||||
|
||||
@@ -5,14 +5,19 @@ import Bridge from "../form";
|
||||
import TransactionHistory from "../transaction-history";
|
||||
import { useNativeBridgeNavigationStore } from "@/stores";
|
||||
import BridgeSkeleton from "./skeleton";
|
||||
import WrongNetwork from "../wrong-network";
|
||||
|
||||
export default function BridgeLayout() {
|
||||
const isTransactionHistoryOpen = useNativeBridgeNavigationStore.useIsTransactionHistoryOpen();
|
||||
const { sdkHasLoaded } = useDynamicContext();
|
||||
const { sdkHasLoaded, primaryWallet } = useDynamicContext();
|
||||
|
||||
if (!sdkHasLoaded) {
|
||||
return <BridgeSkeleton />;
|
||||
}
|
||||
|
||||
if (primaryWallet && primaryWallet.connector.connectedChain !== "EVM") {
|
||||
return <WrongNetwork />;
|
||||
}
|
||||
|
||||
return isTransactionHistoryOpen ? <TransactionHistory /> : <Bridge />;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@ import TransactionCircleIcon from "@/assets/icons/transaction-circle.svg";
|
||||
|
||||
export default function WrongNetwork() {
|
||||
return (
|
||||
<div className={styles["content"]}>
|
||||
<span className={styles["icon"]}>
|
||||
<TransactionCircleIcon />
|
||||
</span>
|
||||
<p className={styles["title"]}>Please switch network.</p>
|
||||
<p className={styles["description"]}>
|
||||
The native bridge only supports the following networks: Ethereum, Sepolia, Linea Sepolia and Linea mainnet.
|
||||
</p>
|
||||
<div className={styles["wrong-network-wrapper"]}>
|
||||
<div className={styles["content"]}>
|
||||
<span className={styles["icon"]}>
|
||||
<TransactionCircleIcon />
|
||||
</span>
|
||||
<p className={styles["title"]}>Please switch network.</p>
|
||||
<p className={styles["description"]}>
|
||||
This bridge doesn't work with Solana. Please switch to the Ethereum or Linea network.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
.wrong-network-wrapper {
|
||||
background-color: var(--v2-color-white);
|
||||
border-radius: 0.625rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 1.5rem;
|
||||
background-color: var(--v2-color-white);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { zeroAddress } from "viem";
|
||||
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
|
||||
import { useDynamicContext, useIsLoggedIn } from "@/lib/dynamic";
|
||||
import { ChainId, LiFiWidget, WidgetSkeleton, type WidgetConfig } from "@/lib/lifi";
|
||||
import { ClientOnly } from "../client-only";
|
||||
import atypTextFont from "@/assets/fonts/atypText";
|
||||
@@ -125,6 +125,7 @@ const widgetConfig: Partial<WidgetConfig> = {
|
||||
hiddenUI: ["appearance", "language"],
|
||||
sdkConfig: {
|
||||
rpcUrls: {
|
||||
[ChainId.SOL]: [CHAINS_RPC_URLS[ChainId.SOL]],
|
||||
[ChainId.ETH]: [CHAINS_RPC_URLS[ChainId.ETH]],
|
||||
[ChainId.LNA]: [CHAINS_RPC_URLS[ChainId.LNA]],
|
||||
[ChainId.ARB]: [CHAINS_RPC_URLS[ChainId.ARB]],
|
||||
@@ -143,7 +144,6 @@ const widgetConfig: Partial<WidgetConfig> = {
|
||||
chains: {
|
||||
deny: [
|
||||
ChainId.BTC,
|
||||
ChainId.SOL,
|
||||
ChainId.PZE,
|
||||
ChainId.MOR,
|
||||
ChainId.FUS,
|
||||
@@ -170,7 +170,8 @@ const widgetConfig: Partial<WidgetConfig> = {
|
||||
};
|
||||
|
||||
export function Widget() {
|
||||
const { setShowAuthFlow } = useDynamicContext();
|
||||
const { setShowAuthFlow, setShowDynamicUserProfile } = useDynamicContext();
|
||||
const isLoggedIn = useIsLoggedIn();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -180,7 +181,7 @@ export function Widget() {
|
||||
...widgetConfig,
|
||||
walletConfig: {
|
||||
onConnect() {
|
||||
setShowAuthFlow(true);
|
||||
isLoggedIn ? setShowDynamicUserProfile(true) : setShowAuthFlow(true);
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const configSchema = z
|
||||
// Feature toggle for CCTPV2 for USDC transfers
|
||||
isCctpEnabled: z.boolean(),
|
||||
infuraApiKey: z.string().nonempty(),
|
||||
alchemyApiKey: z.string().nonempty(),
|
||||
dynamicEnvironmentId: z.string().nonempty(),
|
||||
lifiApiKey: z.string().nonempty(),
|
||||
onRamperApiKey: z.string().nonempty(),
|
||||
|
||||
@@ -67,6 +67,7 @@ export const config: Config = {
|
||||
},
|
||||
isCctpEnabled: process.env.NEXT_PUBLIC_IS_CCTP_ENABLED === "true",
|
||||
infuraApiKey: process.env.NEXT_PUBLIC_INFURA_ID ?? "",
|
||||
alchemyApiKey: process.env.NEXT_PUBLIC_ALCHEMY_ID ?? "",
|
||||
dynamicEnvironmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID ?? "",
|
||||
lifiApiKey: process.env.NEXT_PUBLIC_LIFI_API_KEY ?? "",
|
||||
onRamperApiKey: process.env.NEXT_PUBLIC_ONRAMPER_API_KEY ?? "",
|
||||
|
||||
@@ -53,7 +53,9 @@ export const CHAINS = [
|
||||
zksync,
|
||||
] as const;
|
||||
|
||||
export const CHAINS_IDS = CHAINS.map((chain) => chain.id);
|
||||
const SOLANA_CHAIN = 1151111081099710 as const;
|
||||
|
||||
export const CHAINS_IDS = [...CHAINS.map((chain) => chain.id), SOLANA_CHAIN];
|
||||
|
||||
export const CHAINS_RPC_URLS: Record<(typeof CHAINS_IDS)[number], string> = {
|
||||
[mainnet.id]: `https://mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
@@ -80,6 +82,7 @@ export const CHAINS_RPC_URLS: Record<(typeof CHAINS_IDS)[number], string> = {
|
||||
[sei.id]: `https://evm-rpc.sei-apis.com`,
|
||||
[sonic.id]: `https://rpc.soniclabs.com`,
|
||||
[zksync.id]: `https://zksync-mainnet.infura.io/v3/${config.infuraApiKey}`,
|
||||
[SOLANA_CHAIN]: `https://solana-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`,
|
||||
};
|
||||
|
||||
export const NATIVE_BRIDGE_SUPPORTED_CHAIN_IDS = [mainnet.id, linea.id, lineaSepolia.id, sepolia.id] as const;
|
||||
|
||||
49
bridge-ui/src/contexts/dynamic-solana-provider.tsx
Normal file
49
bridge-ui/src/contexts/dynamic-solana-provider.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { type PropsWithChildren, useCallback, useEffect, useMemo } from "react";
|
||||
import { type Wallet, useDynamicContext, useDynamicEvents, type SolanaWalletConnector } from "@/lib/dynamic";
|
||||
import { useWallet } from "@/lib/solana";
|
||||
|
||||
const getSolanaConnector = (wallet: Wallet | null): SolanaWalletConnector | undefined => {
|
||||
if (wallet?.connector.connectedChain === "SOL") {
|
||||
return wallet.connector as SolanaWalletConnector;
|
||||
}
|
||||
};
|
||||
|
||||
type DynamicSolanaProviderProps = PropsWithChildren;
|
||||
|
||||
export function DynamicSolanaProvider({ children }: DynamicSolanaProviderProps) {
|
||||
const { disconnect, select, wallets } = useWallet();
|
||||
|
||||
const { primaryWallet } = useDynamicContext();
|
||||
|
||||
useDynamicEvents("logout", () => {
|
||||
disconnect();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (primaryWallet?.connector.connectedChain !== "SOL") {
|
||||
disconnect();
|
||||
}
|
||||
}, [primaryWallet?.connector.connectedChain, disconnect]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const solanaWallet = useMemo(() => getSolanaConnector(primaryWallet), [primaryWallet?.connector.connectedChain]);
|
||||
|
||||
const handleConnectedSolanaWallet = useCallback(async () => {
|
||||
if (!solanaWallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wallet = wallets.find((wallet) => wallet.adapter.name === solanaWallet.name);
|
||||
if (wallet) {
|
||||
select(wallet.adapter.name);
|
||||
}
|
||||
}, [solanaWallet, wallets, select]);
|
||||
|
||||
useEffect(() => {
|
||||
handleConnectedSolanaWallet();
|
||||
}, [handleConnectedSolanaWallet]);
|
||||
|
||||
return children;
|
||||
}
|
||||
21
bridge-ui/src/contexts/solana-provider.tsx
Normal file
21
bridge-ui/src/contexts/solana-provider.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { type Adapter, WalletAdapterNetwork, ConnectionProvider, WalletProvider, clusterApiUrl } from "@/lib/solana";
|
||||
import { DynamicSolanaProvider } from "./dynamic-solana-provider";
|
||||
|
||||
const endpoint = clusterApiUrl(WalletAdapterNetwork.Mainnet);
|
||||
|
||||
const wallets: Adapter[] = [];
|
||||
|
||||
type SolanaWalletProviderProps = PropsWithChildren;
|
||||
|
||||
export function SolanaWalletProvider({ children }: SolanaWalletProviderProps) {
|
||||
return (
|
||||
<ConnectionProvider endpoint={endpoint}>
|
||||
<WalletProvider wallets={wallets} autoConnect>
|
||||
<DynamicSolanaProvider>{children}</DynamicSolanaProvider>
|
||||
</WalletProvider>
|
||||
</ConnectionProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { WagmiProvider } from "wagmi";
|
||||
import { DynamicWagmiConnector, EthereumWalletConnectors, DynamicContextProvider } from "@/lib/dynamic";
|
||||
import {
|
||||
DynamicWagmiConnector,
|
||||
EthereumWalletConnectors,
|
||||
DynamicContextProvider,
|
||||
SolanaWalletConnectors,
|
||||
} from "@/lib/dynamic";
|
||||
import { config as wagmiConfig } from "@/lib/wagmi";
|
||||
import { config } from "@/config";
|
||||
import { SolanaWalletProvider } from "./solana-provider";
|
||||
|
||||
type Web3ProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
type Web3ProviderProps = PropsWithChildren;
|
||||
|
||||
export const cssOverrides = `
|
||||
.connect-button {
|
||||
@@ -64,15 +68,17 @@ export function Web3Provider({ children }: Web3ProviderProps) {
|
||||
<DynamicContextProvider
|
||||
settings={{
|
||||
environmentId: config.dynamicEnvironmentId,
|
||||
walletConnectors: [EthereumWalletConnectors],
|
||||
walletConnectors: [EthereumWalletConnectors, SolanaWalletConnectors],
|
||||
initialAuthenticationMode: "connect-only",
|
||||
mobileExperience: "redirect",
|
||||
appName: "Linea Bridge",
|
||||
cssOverrides,
|
||||
}}
|
||||
>
|
||||
<WagmiProvider config={wagmiConfig}>
|
||||
<DynamicWagmiConnector>{children}</DynamicWagmiConnector>
|
||||
<WagmiProvider config={wagmiConfig} reconnectOnMount={false}>
|
||||
<DynamicWagmiConnector>
|
||||
<SolanaWalletProvider>{children}</SolanaWalletProvider>
|
||||
</DynamicWagmiConnector>
|
||||
</WagmiProvider>
|
||||
</DynamicContextProvider>
|
||||
);
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
export * from "@dynamic-labs/sdk-react-core";
|
||||
export * from "@dynamic-labs/ethereum";
|
||||
export * from "@dynamic-labs/wagmi-connector";
|
||||
export * from "@dynamic-labs/solana";
|
||||
|
||||
5
bridge-ui/src/lib/solana.ts
Normal file
5
bridge-ui/src/lib/solana.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
"use client";
|
||||
|
||||
export * from "@solana/wallet-adapter-base";
|
||||
export * from "@solana/wallet-adapter-react";
|
||||
export * from "@solana/web3.js";
|
||||
1467
pnpm-lock.yaml
generated
1467
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user