Widget customization reference
Every LayerHop embed URL parameter, its range and default, the matching @layerhop/widget option, the postMessage events and the setParams contract (embed protocol 1.1).
The LayerHop widget is the swap card served at https://layerhop.com/embed, configured entirely through URL parameters and reporting back to your page with postMessage events. This page lists every parameter, option and event of embed protocol 1.1 (ready.version is 1.1.0); for a step-by-step setup see Embed the widget, or design it visually in the widget builder.
How are parameters validated?#
Each parameter is checked on its own. The embed ignores an invalid value and uses that parameter's default, so a typo never breaks the card. The contract is additive: parameters and events are only ever added, never renamed or removed.
The @layerhop/widget package is stricter: buildEmbedUrl(), mount() and <LayerHopWidget /> throw a LayerHopWidgetError on an invalid option, so integration mistakes show up during development.
| Value type | Format |
|---|---|
| Color | Hex without #, 3 or 6 digits (0af, 22d3ee). The package also accepts a leading # and strips it. |
| Flag | 1 turns it on. Omit the parameter to leave it off (showUsd=0 is the one flag that turns something off). |
| Chain | A chain slug (base) or numeric id (8453). Solana's id is 7565164. See Supported chains. |
| Token | A symbol from the token list (USDC), or a contract address / Solana mint. |
| List | Comma-separated values, no spaces needed (base,arbitrum). |
Swap parameters#
These are the same parameters as a swap deep link.
| Param | Example | Meaning |
|---|---|---|
srcChain |
base |
Chain the user pays from. |
srcToken |
USDC |
Token the user pays with. |
dstChain |
solana |
Chain the user receives on. |
dstToken |
SOL |
Token the user receives. |
amount |
100 |
Amount in human units (decimal string, . separator), not base units. |
recipient |
9WzD… |
Prefilled destination address. Editable, with a warning when it is not the connected wallet, unless lockRecipient=1. |
slippageBps |
50 |
Slippage tolerance in basis points (integer, 0–10000). |
defaultSlippage |
50 |
Alias of slippageBps; slippageBps wins when both are set. |
integrator |
myapp |
Your partner id, [a-z0-9-]{1,32}. Attribution, plus your partner fee when the id is a registered partner. Unknown ids are ignored. |
The embed always quotes on the widget fee channel (0.30% LayerHop fee). A registered partner's fee is added on top and shown as its own line.
Theme parameters#
| Param | Values | Default | Meaning |
|---|---|---|---|
theme |
dark, light, auto |
dark, or the preset's default |
auto follows the visitor's prefers-color-scheme. |
preset |
midnight, paper, lime, ocean, mono |
none | A full palette for dark and light mode. paper defaults to light, the others to dark. Explicit colors override it. |
accent |
color | theme | Buttons and highlights. |
accentText |
color | automatic | Text on the accent. If it would read below 4.5:1 against the accent, the embed uses dark ink or white instead. |
bg |
color | transparent | Page background behind the card. Presets never set it. |
surface |
color | theme | Card background. |
surfaceAlt |
color | theme | Fields and nested panels. |
text |
color | theme | Main text. |
textMuted |
color | theme | Secondary text. |
border |
color, 0 or 1 |
theme | A hex value sets the border color. 0 removes the card outline, 1 makes it stronger. |
success |
color | theme | Links, success states and the secondary accent. |
warning |
color | theme | Warnings. |
danger |
color | theme | Errors. |
radius |
0–40 (px) | theme | Card corner radius. |
radiusInner |
0–32 (px) | theme | Field and button corner radius. |
font |
inter, system, space-grotesk, ibm-plex-sans, dm-sans, manrope |
theme | ibm-plex-sans, dm-sans and manrope load from Google Fonts only when chosen. |
fontScale |
0.875–1.125 | 1 |
Scales the whole card. |
density |
compact, comfortable |
comfortable |
compact tightens fields and the main button. |
width |
320–720 (px) | 480 |
Maximum card width. |
shadow |
none, soft, strong |
theme | Card shadow. |
Focus rings always use a color with at least 3:1 contrast on the card: the accent, then success, then text.
Content and behaviour parameters#
| Param | Example | Meaning |
|---|---|---|
title |
Deposit to Acme |
Header text, plain text up to 32 characters. Longer values are ignored. |
logo |
https://acme.xyz/logo.svg |
Header image, shown at 24 × 24 px. Absolute https URL without credentials; loaded with referrerpolicy=no-referrer and hidden if it fails. |
hideHeader |
1 |
Hides the header (title, wallet button, history, settings). Wallets connect from the main button. |
buttonLabel |
Deposit |
Main button label once the swap is ready to review, up to 24 characters. |
showUsd |
0 |
Hides USD values (default 1). |
fromChains |
base,arbitrum |
Only these chains on the pay side. |
toChains |
solana |
Only these chains on the receive side. |
excludeChains |
bsc |
Never offer these chains, on either side. |
fromTokens |
base:USDC,USDT |
Only these tokens on the pay side: chain:addressOrSymbol entries, or a bare symbol for any allowed chain. With only chain:-qualified entries, the side's chains are limited to the ones named. |
toTokens |
base:USDC |
Same, for the receive side. |
lockSrc |
1 |
The pay chain and token can't be changed. |
lockDst |
1 |
The receive chain and token can't be changed. |
lockAmount |
1 |
The amount can't be edited. |
lockRecipient |
1 |
With a valid recipient, the recipient is fixed and the receive side stays on that address's VM (EVM or Solana). Ignored without a valid recipient. |
hideRoutes |
1 |
Shows only the selected route's summary, not the alternatives. |
hideSettings |
1 |
Hides the slippage settings. |
hideHistory |
1 |
Hides the history panel. |
hidePoweredBy |
1 |
Hides "Routed by LayerHop". Needs integrator, and applies once a quote confirms it is a registered partner. |
locale |
en |
Interface language. Only en today; anything else falls back to en. |
allowParentOrigin |
https://acme.xyz |
The one origin allowed to send setParams messages. https only (http for localhost), no path. |
When an allow-list or a locked recipient rules out the linked or default tokens, the card starts on the first allowed asset. Allow-lists also apply to wallet-based defaults, the flip button and setParams.
How do package options map to URL parameters?#
@layerhop/widget options use the URL parameter names, with these exceptions:
| Package option | URL parameter | Why |
|---|---|---|
headerTitle |
title |
title is the iframe's title attribute in mount(). |
borderColor |
border (hex) |
border is the outline flag (true → 1, false → 0). |
Booleans (lockDst: true) |
lockDst=1 |
showUsd: false becomes showUsd=0. |
Arrays (fromChains: ["base", 42161]) |
fromChains=base,42161 |
|
liveUpdates: true |
allowParentOrigin=<your origin> |
Set for you from location.origin. |
Options that only exist in the package (they never reach the URL):
| Option | Default | Meaning |
|---|---|---|
baseUrl |
https://layerhop.com |
Origin (optionally with a path prefix) serving /embed. |
autoResize |
true |
Resize the iframe height on resize events. |
height |
640 |
Initial iframe height in px, before the first resize event. |
maxWidth |
480 px (or width + 16) |
CSS max-width of the iframe. |
title |
LayerHop swap |
The iframe's accessible title. |
liveUpdates |
false |
Let update() apply amount, token, theme and color changes in place through setParams, without reloading. |
onReady, onQuote, onSubmitted, onStatus, onError, onResize, onRoutes, onWalletConnected, onWalletDisconnected, onAmountChanged |
none | One callback per event. |
onEvent |
none | Called for every valid event, after the typed callback. |
For the <script> tag, every option is a kebab-case data- attribute on an element marked data-layerhop-widget: srcChain → data-src-chain, borderColor → data-border-color. Lists are comma-separated. A flag is on when the attribute is empty, 1, true or yes, and off with 0.
Which events does the widget send?#
The embed posts { source: "layerhop", type, payload } to window.parent with target origin "*". Payloads never contain secrets.
type |
Payload | When |
|---|---|---|
ready |
{ version } |
The card has loaded. version is the protocol version, 1.1.0 today. |
quote |
{ provider, srcChain, dstChain, amount, dstAmount } |
A new best quote is shown. |
routes |
{ count, provider } |
Routes answered: how many, and the best route's provider. |
submitted |
{ provider, srcChainId, srcTxHash } |
The user's wallet broadcast the source transaction. |
status |
{ provider, srcTxHash, state } |
Order state changed: pending, filled, refunded or failed. |
error |
{ code, message } |
Something failed. code is a stable error code. |
resize |
{ height } |
Content height in px changed. |
walletConnected |
{ vm, address } |
A wallet connected. vm is evm or svm. |
walletDisconnected |
{ vm, address } |
A wallet disconnected. |
amountChanged |
{ amount, srcChain, srcToken } |
The user edited the amount (human units, "" when cleared; chain slug; token address). Debounced, about 400 ms. |
routes, walletConnected, walletDisconnected and amountChanged were added in protocol 1.1. With the script tag, each event is also dispatched on the host element as a bubbling DOM CustomEvent named layerhop:<type>, with the payload in event.detail.
Always check event.data.source === "layerhop" and, in production, event.origin === "https://layerhop.com". A connected wallet address is public data, but walletConnected does tell your page which address connected.
How does the host page control the widget?#
Parent → embed messages are opt-in. The embed only listens when its URL has allowParentOrigin, and only accepts a message when event.origin equals that origin exactly and event.source is its parent window.
iframe.contentWindow.postMessage(
{ source: "layerhop-host", type: "setParams", payload: { amount: "250", dstToken: "USDT", accent: "38bdf8" } },
"https://layerhop.com",
);type |
Payload keys (all optional) |
|---|---|
setParams |
amount (human units, "" clears it), srcChain, srcToken, dstChain, dstToken, theme, accent, accentText, bg, surface, surfaceAlt, text, textMuted, border, success, warning, danger |
Other keys and invalid values are ignored. Locks and allow-lists still apply: a locked side or a locked amount ignores changes. The integrator, recipient, locks and fees can never be changed this way.
With the package, mount(el, { liveUpdates: true }) sets allowParentOrigin for you, and widget.update() sends setParams for the live keys above and reloads the iframe only when another option changed.
What iframe settings does the widget need?#
| Setting | Value | Why |
|---|---|---|
color-scheme (CSS) |
normal |
The embed page is transparent with a normal root color scheme. A different scheme on the iframe makes browsers paint an opaque white or black box behind the card. The package sets it. |
allow |
clipboard-write |
Lets the user copy addresses and transaction hashes. |
| Height | 640 px to start, then follow resize events |
The card grows when panels open. |
title |
Any descriptive text | Accessible name for screen readers. |
Last updated