Embed in one snippet
Paste the iframe where the swap should appear. Visitors connect their own wallet and sign every transaction themselves: funds never pass through your site or through ChainBridge. The script tag does the same thing and is handy in a CMS where you can only add HTML attributes.
<style="color:#e06c75">iframe src=style="color:#98c379">"https:style="color:#5c6370style="color:#98c379">">//www.chainbridge.trading/widget?sellToken=ETH&buyToken=USDC&chainId=style="color:#d19a66">8453&theme=auto" width=style="color:#98c379">"style="color:#d19a66">420" height=style="color:#98c379">"style="color:#d19a66">560" title=style="color:#98c379">"Swap tokens" allow=style="color:#98c379">"clipboard-write" loading=style="color:#98c379">"lazy" style=style="color:#98c379">"border: style="color:#d19a66">0; border-radius: 16px" ></style="color:#e06c75">iframe>
A height of 520 to 600 px fits the swap card without scrolling; widths from 360 px work. If your site sends a Content-Security-Policy, allow https://www.chainbridge.trading in frame-src (and in script-src for widget.js).
URL parameters
Every parameter is optional. Without any, the widget opens on Ethereum with the light theme and the standard platform fee.
| Parameter | Values | Description | Script attribute |
|---|---|---|---|
sellToken | Symbol | ETH, USDC, WBTC... Matched against the built-in token list of the chain; unknown symbols are ignored. | data-sell-token |
buyToken | Symbol | Token preselected on the receive side. | data-buy-token |
chainId | Number | 1, 10, 56, 137, 8453, 42161, 43114 or 59144. Default 1 (Ethereum). | data-chain-id |
theme | light | dark | auto | Default light. auto follows the visitor’s OS setting. | data-theme |
feeRecipient | Address | Your partner wallet. Must be allowlisted by ChainBridge, otherwise quotes are rejected. | data-fee-recipient |
feeBps | 0 to 300 | Your fee in basis points (100 = 1%). Ignored without an allowlisted feeRecipient. | data-fee-bps |
With the script tag, data-width and data-height set the iframe size (default 420 × 520 px).
Partner fees
Partners earn a fee on every swap their users make in the widget. The fee is taken on-chain in the swap transaction and paid to your address on the network of the swap: no invoices, no payout schedule.
Register your fee address
Ask ChainBridge to add your address to the partner allowlist. Until it is listed, leave
feeRecipientout: the quote API rejects unregistered recipients, so the widget would show no quotes.Add feeRecipient and feeBps
feeBpsgoes from 0 to 300 (3%). Your fee replaces the ChainBridge platform fee (0.05% under $1,000, 0.1% from $1,000 to $9,999, 0.2% from $10,000) on those swaps; it is not added on top.Users see it before they sign
The fee is part of the quote and shown on the review screen. Positive slippage stays with the user.
| Volume through your widget (example) | Your fee | You earn |
|---|---|---|
| $50,000 | 25 bps (0.25%) | $125 |
| $250,000 | 25 bps (0.25%) | $625 |
| $250,000 | 50 bps (0.50%) | $1,250 |
Events
The widget posts messages to the page that embeds it. Every message has the same envelope: { source: 'chainbridge-widget', type, data?, timestamp }.
| type | data | When |
|---|---|---|
widget:ready | (none) | The widget has loaded. |
wallet:connected | { address, chainId } | A wallet connects or changes account or network. |
wallet:disconnected | (none) | The wallet disconnects. |
swap:started | { sellToken, buyToken, sellAmount } | The user confirmed a swap. Tokens are symbols, amount is human-readable. |
swap:success | { txHash, sellToken, buyToken, sellAmount, buyAmount, chainId } | The swap settled. txHash can be missing for a gasless order. |
swap:error | { error, isUserCancellation } | The swap failed or the user rejected it in the wallet. |
Swap events cover same-chain swaps; cross-chain transfers started in the widget do not emit them.
React example
A typed component that builds the URL and forwards events after three checks. It works in any React app, including Next.js client components.
'use client' import { useEffect, useRef } from 'react' const WIDGET_ORIGIN = 'https://www.chainbridge.trading' type WidgetEvent = { source: 'chainbridge-widget' type: 'widget:ready' | 'wallet:connected' | 'wallet:disconnected' | 'swap:started' | 'swap:success' | 'swap:error' data?: Record<string, unknown> timestamp: number } export function SwapWidget({ onEvent }: { onEvent?: (e: WidgetEvent) => void }) { const frame = useRef<HTMLIFrameElement>(null) useEffect(() => { const handler = (e: MessageEvent) => { if (e.origin !== WIDGET_ORIGIN) return "color:#5c6370">// 1. right origin if (e.source !== frame.current?.contentWindow) return "color:#5c6370">// 2. our iframe, not another one if (e.data?.source !== 'chainbridge-widget') return "color:#5c6370">// 3. widget envelope onEvent?.(e.data as WidgetEvent) } window.addEventListener('message', handler) return () => window.removeEventListener('message', handler) }, [onEvent]) const params = new URLSearchParams({ sellToken: 'ETH', buyToken: 'USDC', chainId: '8453', theme: 'auto', feeRecipient: '0xYourPartnerAddress', "color:#5c6370">// must be allowlisted feeBps: '25', }) return ( <iframe ref={frame} src={`${WIDGET_ORIGIN}/widget?${params}`} title="Swap tokens" allow="clipboard-write" className="h-[560px] w-full max-w-[440px] rounded-2xl border-0" /> ) }
<SwapWidget
onEvent={(e) => {
if (e.type === 'swap:success') analytics.track('swap', e.data)
if (e.type === 'swap:error' && !e.data?.isUserCancellation) showHelp()
}}
/>Security notes
- Check the origin. Accept a message only if
event.originis exactlyhttps://www.chainbridge.tradingandevent.sourceis your iframe. Any page or frame can callpostMessage. - Treat events as hints, not proof. Before crediting a user or granting a reward, verify
txHashon-chain with your own RPC. - Do not ask for keys or seed phrases. The widget never needs them; the user's wallet signs inside the iframe.
- Safety checks stay on. Token safety screening, transaction simulation and the contract allowlist run in the widget exactly as on ChainBridge. See token safety checks.
- Handle personal data with care.
wallet:connectedgives you the visitor's address. Treat it as personal data under your privacy policy.
Need quotes without a UI? Use the REST API directly.