LI.FI Widget Light (@lifi/widget-light) lets you embed the full LI.FI cross-chain swap and bridge widget into your application via an iframe, while keeping wallet connections on your side. Your users sign transactions with their existing wallet — the widget never touches browser extensions or private keys directly.
- Iframe isolation — the widget runs in a sandboxed iframe; your page stays in full control
- Multi-ecosystem support — Ethereum (EVM), Solana, Bitcoin, and Sui out of the box
- Your wallet, your UX — transactions are signed by wallets you already manage (wagmi, wallet-standard, bigmi, dapp-kit)
- Zero core dependencies —
@lifi/widget-lightships nothing beyond React as a peer dep; chain-specific handlers are tree-shakeable subpath imports - Reactive configuration — update config at any time without reloading the iframe
- Typed event system — subscribe to route execution, wallet, and UI events with full TypeScript support
┌─────────────────────────────────┐ ┌────────────────────────────────┐
│ YOUR APPLICATION │ │ IFRAME (widget.li.fi) │
│ │ │ │
│ LiFiWidgetLight component │◄─────►│ Full LI.FI Widget │
│ + Ecosystem handlers (wagmi…) │ post │ Receives config, sends RPC │
│ + Event subscriptions │ Msg │ requests back to your wallets │
└─────────────────────────────────┘ └────────────────────────────────┘
- Your app renders
<LiFiWidgetLight>pointing at the hosted widget URL - The iframe sends a
READYsignal; your app responds with config + wallet state - When the widget needs a signature or chain switch, it sends an RPC request via
postMessage - Your ecosystem handler (e.g. wagmi) executes the request and returns the result
- Wallet state changes (account switch, network change) are pushed to the iframe automatically
This is the minimal setup for EVM chains using wagmi. See Full Multi-Ecosystem Setup for all chains.
pnpm add @lifi/widget-light wagmi viem @wagmi/core @tanstack/react-querySet up wagmi as you normally would. Widget Light reads wallet state from your existing wagmi context.
// providers/WalletProvider.tsx
import type { FC, PropsWithChildren } from 'react'
import { createClient, http } from 'viem'
import { arbitrum, base, mainnet, optimism, polygon } from 'viem/chains'
import { createConfig, WagmiProvider } from 'wagmi'
import { injected } from 'wagmi/connectors'
const config = createConfig({
chains: [mainnet, arbitrum, optimism, base, polygon],
connectors: [injected()],
client({ chain }) {
return createClient({ chain, transport: http() })
},
multiInjectedProviderDiscovery: true,
ssr: false,
})
export const WalletProvider: FC<PropsWithChildren> = ({ children }) => (
<WagmiProvider config={config}>{children}</WagmiProvider>
)// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App'
import { WalletProvider } from './providers/WalletProvider'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<WalletProvider>
<App />
</WalletProvider>
</QueryClientProvider>
</React.StrictMode>
)// App.tsx
import { LiFiWidgetLight } from '@lifi/widget-light'
import type { WidgetLightConfig } from '@lifi/widget-light'
import { useEthereumIframeHandler } from '@lifi/widget-light/ethereum'
import { useMemo } from 'react'
const widgetConfig: WidgetLightConfig = {
integrator: 'your-project-name', // Required — identifies you in LI.FI analytics
variant: 'wide', // 'compact' | 'wide' | 'drawer'
theme: {
container: {
border: '1px solid rgb(234, 234, 234)',
borderRadius: '16px',
},
},
sdkConfig: {
routeOptions: {
maxPriceImpact: 0.4,
},
},
}
export function App() {
const ethHandler = useEthereumIframeHandler()
const handlers = useMemo(() => [ethHandler], [ethHandler])
return (
<LiFiWidgetLight
config={widgetConfig}
handlers={handlers}
autoResize
/>
)
}That's it. The widget renders inside an iframe and all EVM transactions are signed through your wagmi-managed wallet.
To support Solana, Bitcoin, and Sui alongside EVM, install the additional handler peer dependencies and pass all handlers to the widget.
# Core
pnpm add @lifi/widget-light @tanstack/react-query
# EVM
pnpm add wagmi viem @wagmi/core
# Solana (optional)
pnpm add @wallet-standard/base
# Bitcoin (optional)
pnpm add @bigmi/client @bigmi/react
# Sui (optional)
pnpm add @mysten/dapp-kit-reactimport { LiFiWidgetLight } from '@lifi/widget-light'
import { useEthereumIframeHandler } from '@lifi/widget-light/ethereum'
import { useSolanaIframeHandler } from '@lifi/widget-light/solana'
import { useBitcoinIframeHandler } from '@lifi/widget-light/bitcoin'
import { useSuiIframeHandler } from '@lifi/widget-light/sui'
import { useMemo, useCallback } from 'react'
export function App() {
// EVM — reads wallet state from wagmi context automatically
const ethHandler = useEthereumIframeHandler()
// Solana — pass wallet state explicitly (library-agnostic)
const solHandler = useSolanaIframeHandler({
address: solanaAddress, // string | undefined
connected: solanaConnected, // boolean
wallet: solanaWallet, // Wallet from @wallet-standard/base
})
// Bitcoin — reads from @bigmi/react context automatically
const btcHandler = useBitcoinIframeHandler()
// Sui — reads from @mysten/dapp-kit-react context automatically
const suiHandler = useSuiIframeHandler()
const handlers = useMemo(
() => [ethHandler, solHandler, btcHandler, suiHandler],
[ethHandler, solHandler, btcHandler, suiHandler]
)
return (
<LiFiWidgetLight
config={widgetConfig}
handlers={handlers}
autoResize
/>
)
}Only include the handlers you need. If you only support EVM and Solana, pass
[ethHandler, solHandler]. Unused ecosystem packages are fully tree-shaken.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
src |
string |
No | 'https://widget.li.fi' |
URL of the hosted widget |
config |
WidgetLightConfig |
Yes | — | JSON-serializable widget configuration |
handlers |
IframeEcosystemHandler[] |
No | [] |
Ecosystem handlers for wallet/RPC bridging |
iframeOrigin |
string |
No | Derived from src |
Restrict postMessage to this origin |
autoResize |
boolean |
No | false |
When true, iframe height auto-adjusts to match content |
onConnect |
(args?: ConnectWalletArgs) => void |
No | — | Called when the widget requests a wallet connection (external wallet management) |
style |
CSSProperties |
No | — | Inline styles for the iframe element |
className |
string |
No | — | CSS class for the iframe element |
title |
string |
No | 'LI.FI Widget' |
Accessible title for the iframe |
WidgetLightConfig must be JSON-serializable — no React nodes, no callback functions, no MUI theme objects.
| Field | Type | Description |
|---|---|---|
integrator |
string |
Your project identifier for LI.FI analytics |
const config: WidgetLightConfig = {
integrator: 'your-project-name',
// Layout
variant: 'wide', // 'compact' | 'wide' | 'drawer'
mode: 'default', // 'default' | 'split' | 'custom' | 'refuel'
appearance: 'light', // 'light' | 'dark' | 'system'
// Pre-fill form
fromChain: 1, // Source chain ID
toChain: 42161, // Destination chain ID
fromToken: '0x...', // Source token address
toToken: '0x...', // Destination token address
fromAmount: '10', // Pre-filled amount
// Theming (CSS properties only — no MUI)
theme: {
container: {
border: '1px solid #eaeaea',
borderRadius: '16px',
},
},
// API and routing
apiKey: 'your-api-key',
fee: 0.03, // Integrator fee (0-1)
sdkConfig: {
routeOptions: {
maxPriceImpact: 0.4,
},
},
// Filter chains, bridges, exchanges
chains: {
allow: [1, 137, 42161], // Only show these chains
},
bridges: {
deny: ['stargate'], // Hide specific bridges
},
// UI controls
hiddenUI: ['appearance', 'language'],
disabledUI: ['toAddress'],
requiredUI: ['toAddress'],
}Configuration is reactive. When you pass a new config object, the widget updates without reloading the iframe.
const [variant, setVariant] = useState<'wide' | 'compact'>('wide')
const widgetConfig = useMemo(
() => ({ ...baseConfig, variant }),
[variant]
)
// Changing `variant` state updates the widget in real-time
<LiFiWidgetLight config={widgetConfig} ... />If your app has its own wallet connection UI, pass onConnect to handle wallet connection requests from the widget. The widget will call your handler instead of opening its built-in wallet menu.
import type { ConnectWalletArgs } from '@lifi/widget-light'
function App() {
const handleConnect = useCallback((args?: ConnectWalletArgs) => {
// Open your wallet modal/dialog
openYourWalletModal()
}, [])
return (
<LiFiWidgetLight
onConnect={handleConnect}
// ...other props
/>
)
}Subscribe to widget events from any component using the useWidgetLightEvents hook. No provider wrapping required — the event bus is a module-level singleton.
import {
useWidgetLightEvents,
WidgetLightEvent,
type WidgetLightRouteExecutionUpdate,
} from '@lifi/widget-light'
import { useEffect } from 'react'
function TransactionTracker() {
const events = useWidgetLightEvents()
useEffect(() => {
const onComplete = (data: WidgetLightRouteExecutionUpdate) => {
console.log('Route completed:', data)
}
events.on(WidgetLightEvent.RouteExecutionCompleted, onComplete)
return () => events.off(WidgetLightEvent.RouteExecutionCompleted, onComplete)
}, [events])
return null
}| Event | Payload Type | Description |
|---|---|---|
RouteExecutionStarted |
WidgetLightRouteExecutionUpdate |
A route has started executing |
RouteExecutionUpdated |
WidgetLightRouteExecutionUpdate |
Step progress update |
RouteExecutionCompleted |
WidgetLightRouteExecutionUpdate |
Route completed successfully |
RouteExecutionFailed |
WidgetLightRouteExecutionUpdate |
Route execution failed |
RouteSelected |
WidgetLightRouteSelected |
User selected a route |
RouteHighValueLoss |
WidgetLightRouteHighValueLoss |
High value loss detected |
SourceChainTokenSelected |
WidgetLightChainTokenSelected |
Source token changed |
DestinationChainTokenSelected |
WidgetLightChainTokenSelected |
Destination token changed |
FormFieldChanged |
WidgetLightFormFieldChanged |
Any form field changed |
WalletConnected |
WidgetLightWalletConnected |
Wallet connected |
WalletDisconnected |
WidgetLightWalletDisconnected |
Wallet disconnected |
ContactSupport |
WidgetLightContactSupport |
User clicked contact support |
AvailableRoutes |
— | Routes fetched and available |
PageEntered |
— | Page navigation |
WidgetExpanded |
— | Widget expanded (drawer variant) |
SendToWalletToggled |
— | Send-to-wallet toggle changed |
SettingUpdated |
WidgetLightSettingUpdated |
User changed a setting |
ChainPinned |
WidgetLightChainPinned |
Chain pinned/unpinned |
TokenSearch |
WidgetLightTokenSearch |
User searched for a token |
LowAddressActivityConfirmed |
WidgetLightLowAddressActivityConfirmed |
Low activity address confirmed |
Each handler implements the IframeEcosystemHandler interface and bridges RPC calls between the iframe and your wallet provider.
import { useEthereumIframeHandler } from '@lifi/widget-light/ethereum'- Reads from: wagmi context (
useConnection,useWalletClient,usePublicClient,useSwitchChain) - Peer deps:
wagmi,viem,@wagmi/core - RPC methods handled:
eth_accounts,eth_requestAccounts,eth_chainId,eth_sendTransaction,personal_sign,eth_signTypedData_v4,wallet_switchEthereumChain,wallet_addEthereumChain,wallet_sendCalls,wallet_getCallsStatus,wallet_getCapabilities, and more - No parameters — all state is read from wagmi hooks
import { useSolanaIframeHandler } from '@lifi/widget-light/solana'- Peer deps:
@wallet-standard/base - Parameters:
address: string | undefined— connected wallet addressconnected: boolean— whether a wallet is connectedwallet: Wallet | undefined— wallet-standardWalletinstance
- RPC methods:
getAccount,signTransaction,signMessage,signAndSendTransaction - Library-agnostic — works with any Solana wallet adapter that provides a wallet-standard
Wallet
import { useBitcoinIframeHandler } from '@lifi/widget-light/bitcoin'- Reads from:
@bigmi/reactcontext (useAccount,useConfig) - Peer deps:
@bigmi/client,@bigmi/react - No parameters
import { useSuiIframeHandler } from '@lifi/widget-light/sui'- Reads from:
@mysten/dapp-kit-reacthooks - Peer deps:
@mysten/dapp-kit-react - RPC methods:
getAccount,signTransaction,signPersonalMessage,signAndExecuteTransaction - No parameters
iframeOrigin is automatically derived from src (defaults to https://widget.li.fi), so postMessage communication is restricted to the correct origin out of the box. If you use a custom src, iframeOrigin will be derived from it automatically. You only need to set iframeOrigin explicitly if you want to override the derived value.
Working examples are available in the repository:
| Example | Description | Path |
|---|---|---|
| vite-iframe-wagmi | Minimal EVM-only integration | examples/vite-iframe-wagmi/ |
| vite-iframe | Full multi-ecosystem with events, config reactivity, and external wallet management | examples/vite-iframe/ |
Run an example locally:
# From the repository root
pnpm install
pnpm --filter vite-iframe-wagmi dev
# or
pnpm --filter vite-iframe devThe default (https://widget.li.fi) is the production-hosted widget — you don't need to set src at all for most use cases. For testing against a specific version or self-hosted deployment, pass your custom URL as src.
Currently, @lifi/widget-light uses a module-level singleton for the event bus and guest bridge. Only one <LiFiWidgetLight> instance per page is supported.
Configuration is sent to the iframe via postMessage, which uses the structured clone algorithm. React nodes, functions, class instances, and MUI theme objects cannot be cloned. Use the WidgetLightConfig type to ensure compatibility — the type system will catch non-serializable values at compile time.
When autoResize is true (default), the iframe content uses a ResizeObserver to detect height changes and posts them to the host. The host directly mutates iframe.style.height for zero-flicker updates. Set autoResize={false} if you want to control iframe dimensions yourself via CSS.
Wallet state is sent from your app to the iframe on every mount via the INIT handshake. If your wagmi/wallet-adapter handles reconnection (which most do by default), the widget will automatically receive the reconnected state.
- LI.FI Widget Documentation
- GitHub Issues
- Widget Playground — interactively explore configuration options