@mobula_labs/bridge-widget
v0.3.3
Published
Embeddable React bridge widget — drop-in cross-chain bridging UI powered by Mobula
Maintainers
Readme
@mobula_labs/bridge-widget
Drop-in cross-chain bridge widget for React, powered by Mobula. Bridge across EVM chains, Solana and Hyperliquid from a single embeddable component — no iframe.
Install
npm install @mobula_labs/bridge-widgetreact and react-dom (>=18) are peer dependencies; everything else (wallet stack, chain
clients) ships with the package.
Usage
import { MobulaBridgeWidget } from '@mobula_labs/bridge-widget';
import '@mobula_labs/bridge-widget/styles.css';
export default function App() {
return <MobulaBridgeWidget config={{ theme: 'dark' }} />;
}The widget mounts its own wallet stack (Privy + Solana adapter) and react-query client. It is fully self-contained — nothing else to wire up.
Wallet: two modes
Like LI.FI's widget, wallets work two ways — you choose:
1. Internal (default) — the user connects inside the widget. Zero config, shown above.
2. Bring your own wallet — your site already has a connected wallet, so you inject it and
the user never reconnects. Pass any EIP-1193 provider (wagmi, RainbowKit, Privy, Dynamic,
window.ethereum) for EVM and/or the standard sign function for Solana:
import { MobulaBridgeWidget } from '@mobula_labs/bridge-widget';
import '@mobula_labs/bridge-widget/styles.css';
import { useAccount, useConnectorClient } from 'wagmi';
function Bridge() {
const { address } = useAccount();
const { data: client } = useConnectorClient();
return (
<MobulaBridgeWidget
config={{
wallet: address && client ? { evm: { address, provider: client.transport } } : undefined,
// Optional: called when the user hits connect and nothing is injected yet
onConnectWallet: () => openMyWalletModal(),
}}
/>
);
}An injected wallet takes priority over the widget's internal flow — signing, chain switching,
and transactions all route through your provider. Inject solana the same way:
wallet: { solana: { address, signTransaction } }. You can inject EVM, Solana, or both.
Config
Every field is optional; sensible defaults ship with the widget.
| Field | Description |
|---|---|
| theme | 'dark' (default) or 'light' |
| variant | 'default' (480px), 'compact' (400px), or 'drawer' (slides in behind a launcher) |
| width | Override the card width (number = px) |
| appearance | Brand overrides: { accentColor?, accentTextColor?, fontFamily?, colors?, borderRadius? } |
| defaults | Initial form: { fromChain?, toChain?, fromToken?, toToken?, amount?, slippage? } |
| chains | Restrict networks: { allow?: string[], deny?: string[] } |
| tokens | Restrict tokens by symbol: { allow?: string[], deny?: string[] } |
| locale | Bundled language: 'en' (default), 'fr', 'es', 'zh' |
| translations | Override any UI string (merged over the active locale) |
| events | Lifecycle callbacks — see below |
| className | Extra class on the widget root |
| apiBase | Mobula API base URL |
| apiKey | Optional — your own Mobula API key. Omit it and the widget authenticates only with short-lived tokens minted by tokenMintUrl (no key embedded in the client) |
| tokenMintUrl | Cloudflare worker that mints short-lived Mobula tokens |
| privyAppId / privyClientId | Your Privy credentials |
| solanaRpcUrl | Solana RPC endpoint |
| evmRpcUrls | { [chainId]: url } overrides |
| relayApiBase | Relay API base URL |
| providers | { queryClient?, skipPrivy?, skipQueryClient?, skipSolanaAdapter? } — opt out of a bundled provider when your app already supplies it |
Prefill, restrict, and brand
Preselect the route, scope which chains/tokens are offered, and repaint the primary
action with your brand color — all optional. Chain references accept a bridgeChainId
(evm:8453) or a friendly name (base, arbitrum, bsc, polygon, solana,
hyperliquid); tokens are matched by symbol.
<MobulaBridgeWidget
config={{
appearance: { accentColor: '#7C3AED', accentTextColor: '#fff' },
defaults: { fromChain: 'base', toChain: 'arbitrum', fromToken: 'USDC', amount: '100' },
chains: { allow: ['base', 'arbitrum', 'solana'] },
tokens: { deny: ['USDT'] },
}}
/>Events
React to the bridge lifecycle from the host — the widget's equivalent of LI.FI's
useWidgetEvents():
<MobulaBridgeWidget
config={{
events: {
onSourceChainChanged: (id) => {},
onDestinationChainChanged: (id) => {},
onSourceTokenChanged: (sym) => {},
onDestinationTokenChanged: (sym) => {},
onBridgeStarted: (e) => {}, // deposit submitted
onBridgeCompleted: (e) => {}, // filled / settled
onBridgeFailed: (e) => {}, // failed / refunded
},
}}
/>Each onBridge* callback receives { provider, sourceChain, targetChain, sourceToken,
targetToken, amount, amountOut?, depositTxHash?, fillTxHash?, intentId?, failureReason? }.
Layout & full theming
variant picks the shell; appearance.colors and appearance.borderRadius are the
design-token equivalents of LI.FI's theme.palette / theme.shape. Any omitted color
keeps the theme default.
<MobulaBridgeWidget
config={{
variant: 'drawer', // 'default' | 'compact' | 'drawer'
width: 420,
appearance: {
accentColor: '#7C3AED',
borderRadius: 12,
colors: {
background: '#0b0b12',
surface: '#15151f',
elevated: '#1c1c2a',
text: '#ffffff',
textMuted: '#9aa0b4',
border: 'rgba(255,255,255,0.08)',
},
},
}}
/>Localization
Pick a bundled language with locale — 'en' (default), 'fr', 'es', or 'zh'
ship complete out of the box:
<MobulaBridgeWidget config={{ locale: 'fr' }} />Need another language, or want to tweak a few strings? translations merges over the
active locale (or English), so you only supply what you change:
<MobulaBridgeWidget
config={{
locale: 'es',
translations: {
cta: { bridge: 'Enviar' }, // override just this string
labels: { from: 'Origen', to: 'Destino' },
},
}}
/>Reusing your own providers
If your app already mounts Privy / react-query / the Solana adapter, opt out so the widget doesn't double-provide:
<MobulaBridgeWidget
config={{ providers: { skipPrivy: true, skipQueryClient: true, skipSolanaAdapter: true } }}
/>License
MIT
