Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions apps/bridge/components/DefaultChainShells.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FC, ReactNode, useMemo } from "react"
import {
createEVMShell,
createStarknetShell,
createFuelShell,
createParadexShell,
createBitcoinShell,
createTONShell,
createSVMShell,
createTronShell,
createImmutablePassportShell,
type WalletProviderShell,
} from "@layerswap/wallets"
import { useRouter } from "next/router"

// Composes every chain shell the bridge ships with into a single nested
// JSX subtree. The order here is the legacy resolution priority from
// getDefaultProviders: a network supported by multiple chains resolves
// to the outermost shell first. Adding/removing chains is a JSX change,
// not a runtime array mutation — the React tree stays stable for the
// lifetime of the app.
//
// Each shell is created via createXxxShell() which routes through
// defineWalletProvider — see packages/widget/src/lib/defineWalletProvider.tsx.

const DefaultChainShells: FC<{ children: ReactNode }> = ({ children }) => {
const router = useRouter()

const shells = useMemo(() => {
const imtblPassportConfig = typeof window !== 'undefined' ? {
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID || '',
publishableKey: process.env.NEXT_PUBLIC_IMMUTABLE_PUBLISHABLE_KEY || '',
redirectUri: router.basePath
? `${window.location.origin}${router.basePath}/imtblRedirect`
: `${window.location.origin}/imtblRedirect`,
logoutRedirectUri: router.basePath
? `${window.location.origin}${router.basePath}/`
: `${window.location.origin}/`,
} : undefined

const walletConnectConfigs = {
projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || '',
name: 'Layerswap',
description: 'Layerswap App',
url: 'https://layerswap.io/app/',
icons: ['https://www.layerswap.io/app/symbol.png'],
}

const tonConfigs = {
tonApiKey: process.env.NEXT_PUBLIC_TON_API_KEY || '',
manifestUrl: 'https://layerswap.io/app/tonconnect-manifest.json',
}

const list: WalletProviderShell[] = [
createEVMShell({ walletConnectConfigs }),
createStarknetShell(),
createFuelShell(),
createParadexShell(),
createBitcoinShell(),
createTONShell({ tonConfigs }),
createSVMShell({ walletConnectConfigs }),
createTronShell(),
]
if (imtblPassportConfig) {
list.push(createImmutablePassportShell({ imtblPassportConfig }))
}
return list
}, [router.basePath])

// Reduce-right builds the JSX tree from the innermost child outward,
// matching the order in `shells`: shells[0] (EVM) becomes the
// outermost wrapper. This is the only place the bridge composes
// chain shells; everything else just renders <DefaultChainShells>.
return <>{shells.reduceRight<ReactNode>(
(acc, Shell) => <Shell>{acc}</Shell>,
children,
)}</>
}

export default DefaultChainShells
11 changes: 9 additions & 2 deletions apps/bridge/components/Pages/Swap/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { LayerSwapSettings, Swap, ThemeData } from "@layerswap/widget"
import { FC } from "react"
import WidgetWrapper from "../../WidgetWrapper"
import DefaultChainShells from "../../DefaultChainShells"
import { QueryParams } from "../../../helpers/querryHelper"

// Chain shells are composed in DefaultChainShells (JSX children of
// LayerswapProvider) — no more async setState of a walletProviders array.
// The shell tree mounts once and stays stable for the lifetime of the
// page, so there is no [] → populated transition to remount the swap UI.
const SwapPage: FC<{ settings: LayerSwapSettings, themeData: ThemeData | null, apiKey: string, initialValues: QueryParams }> = ({ settings, themeData, apiKey, initialValues }) => {
return (
<WidgetWrapper
Expand All @@ -12,9 +17,11 @@ const SwapPage: FC<{ settings: LayerSwapSettings, themeData: ThemeData | null, a
initialValues={initialValues}
enableSwapCallbacks
>
<Swap />
<DefaultChainShells>
<Swap />
</DefaultChainShells>
</WidgetWrapper>
)
}

export default SwapPage
export default SwapPage
38 changes: 6 additions & 32 deletions apps/bridge/components/WidgetWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { LayerswapProvider, LayerSwapSettings, ThemeData } from "@layerswap/widget"
import { LayerswapProvider, LayerSwapSettings, ThemeData } from "@layerswap/widget/transactions"
import { useRouter } from "next/router"
import { ComponentProps, ReactNode } from "react"
import { updateFormBulk } from "./utils/updateForm"
import { removeSwapPath, setMenuPath, setSwapPath } from "./utils/updatePath"
import { getDefaultProviders } from "@layerswap/wallets";
import { QueryParams } from "../helpers/querryHelper"
import { logError } from "./utils/logError"

Expand All @@ -16,50 +15,26 @@ type WidgetWrapperProps<T extends Record<string, unknown> = Record<string, never
apiKey?: string;
initialValues?: QueryParams;
callbacks?: LayerswapProviderComponentProps['callbacks'];
walletProviders?: LayerswapProviderComponentProps['walletProviders'];
configOverrides?: Partial<LayerswapProviderComponentProps['config']>;
enableSwapCallbacks?: boolean;
};

// WidgetWrapper no longer manages wallet providers — chain shells are
// composed as JSX children by the caller (see DefaultChainShells.tsx and
// the swap pages). LayerswapProvider's children include the shell tree
// and the page content.
const WidgetWrapper = <T extends Record<string, unknown>>({
children,
settings,
themeData,
apiKey,
initialValues,
callbacks,
walletProviders,
configOverrides,
enableSwapCallbacks = false,
}: WidgetWrapperProps<T>) => {
const router = useRouter()

const imtblPassportConfig = typeof window !== 'undefined' ? {
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID || '',
publishableKey: process.env.NEXT_PUBLIC_IMMUTABLE_PUBLISHABLE_KEY || '',
redirectUri: router.basePath ? `${window.location.origin}${router.basePath}/imtblRedirect` : `${window.location.origin}/imtblRedirect`,
logoutRedirectUri: router.basePath ? `${window.location.origin}${router.basePath}/` : `${window.location.origin}/`
} : undefined

const walletConnectConfigs = {
projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || '',
name: 'Layerswap',
description: 'Layerswap App',
url: 'https://layerswap.io/app/',
icons: ['https://www.layerswap.io/app/symbol.png']
}

const defaultWalletProviders = getDefaultProviders({
walletConnect: walletConnectConfigs,
immutablePassport: imtblPassportConfig,
ton: {
tonApiKey: process.env.NEXT_PUBLIC_TON_API_KEY || '',
manifestUrl: 'https://layerswap.io/app/tonconnect-manifest.json'
}
})

const resolvedWalletProviders = walletProviders ?? defaultWalletProviders

const themeOverrides: Partial<ThemeData> = {
borderRadius: 'default',
enablePortal: true,
Expand Down Expand Up @@ -110,10 +85,9 @@ const WidgetWrapper = <T extends Record<string, unknown>>({
return <LayerswapProvider
config={mergedConfig}
callbacks={resolvedCallbacks}
walletProviders={resolvedWalletProviders}
>
{children}
</LayerswapProvider>
}

export default WidgetWrapper;
export default WidgetWrapper;
2 changes: 2 additions & 0 deletions apps/bridge/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ module.exports = (phase, { defaultConfig }) => {
'@radix-ui/react-select',
'@radix-ui/react-tabs',
'@radix-ui/react-tooltip',
'@layerswap/widget',
'@layerswap/wallets',
],
},
webpack: config => {
Expand Down
2 changes: 1 addition & 1 deletion apps/bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@vercel/speed-insights": "^1.3.1",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"framer-motion": "^10.16.15",
"framer-motion": "catalog:",
"lucide-react": "^0.379.0",
"next": "15.5.9",
"posthog-js": "^1.272.0",
Expand Down
43 changes: 25 additions & 18 deletions apps/bridge/pages/imtblRedirect.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { THEME_COLORS } from "@layerswap/widget";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useState } from "react";
import { createEVMProvider, createImmutablePassportProvider, ImtblRedirect } from "@layerswap/wallets";
import { useEffect, useMemo, useState } from "react";
import { createEVMShell, createImmutablePassportShell, ImtblRedirect } from "@layerswap/wallets";
import WidgetWrapper from "../components/WidgetWrapper";

const ImtblRedirectPage = () => {
Expand All @@ -13,37 +12,45 @@ const ImtblRedirectPage = () => {
setLoaded(true)
}, [])

if (!loaded) return <div>Loading...</div>
const themeData = THEME_COLORS['default']

const walletProviders = [
createEVMProvider({
// Build the shells inside a useMemo to keep their identity stable —
// each shell wraps state internally (wagmi config etc.), so a new
// instance every render would cause unnecessary remounts.
const shells = useMemo(() => {
if (!loaded) return null
const EVMShell = createEVMShell({
walletConnectConfigs: {
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || '',
name: 'Layerswap',
description: 'Layerswap App',
url: 'https://layerswap.io/app/',
icons: ['https://www.layerswap.io/app/symbol.png']
}
}),
createImmutablePassportProvider({
})
const ImmutablePassportShell = createImmutablePassportShell({
imtblPassportConfig: {
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID,
publishableKey: process.env.NEXT_PUBLIC_IMMUTABLE_PUBLISHABLE_KEY,
redirectUri: basePath ? `${window.location.hostname}${basePath}/imtblRedirect` : `${window.location.hostname}/imtblRedirect`,
logoutRedirectUri: basePath ? `${window.location.hostname}${basePath}/` : `${window.location.hostname}/`
logoutRedirectUri: basePath ? `${window.location.hostname}${basePath}/` : `${window.location.hostname}/`,
}
})
]
return { EVMShell, ImmutablePassportShell }
}, [loaded, basePath])

if (!loaded || !shells) return <div>Loading...</div>
const themeData = THEME_COLORS['default']

const { EVMShell, ImmutablePassportShell } = shells

return (
<WidgetWrapper
themeData={themeData}
walletProviders={walletProviders}
>
<ImtblRedirect />
<WidgetWrapper themeData={themeData}>
<EVMShell>
<ImmutablePassportShell>
<ImtblRedirect />
</ImmutablePassportShell>
</EVMShell>
</WidgetWrapper>
);
}

export default ImtblRedirectPage;
export default ImtblRedirectPage;
5 changes: 4 additions & 1 deletion apps/bridge/pages/swap/[swapId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Layout from '../../components/layout';
import { useRouter } from 'next/router';
import { resolvePersistantQueryParams } from '../../helpers/querryHelper';
import WidgetWrapper from '../../components/WidgetWrapper';
import DefaultChainShells from '../../components/DefaultChainShells';
import MaintananceContent from '../../components/maintanance/maintanance';


Expand Down Expand Up @@ -34,7 +35,9 @@ const SwapDetails = ({ settings, themeData, apiKey, swapData }: InferGetServerSi
}
}}
>
<SwapWithdrawal initialSwapData={swapData} />
<DefaultChainShells>
<SwapWithdrawal initialSwapData={swapData} />
</DefaultChainShells>
</WidgetWrapper>
</Layout>
</>
Expand Down
2 changes: 1 addition & 1 deletion apps/bridge/pages/transactions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InferGetServerSidePropsType } from 'next'
import { getServerSideProps } from '../helpers/getSettings'
import { TransactionsHistory, inflateSettings } from '@layerswap/widget';
import { TransactionsHistory, inflateSettings } from '@layerswap/widget/transactions';
import Layout from '../components/layout';
import { useRouter } from 'next/router';
import { resolvePersistantQueryParams } from '../helpers/querryHelper';
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"framer-motion": "^10.16.15",
"framer-motion": "catalog:",
"heroicons": "^2.0.18",
"lucide-react": "^0.379.0",
"next": "15.5.9",
Expand Down
Loading
Loading