Guides
Embed the widget
Put a LayerHop swap card in any site with an iframe, configure it with URL parameters, and listen to its postMessage events.
The embed is a chromeless swap card — no header, no footer — served by the LayerHop web app at /embed. It uses the widget fee channel. Its URL parameters and events are a public contract (docs/URL_CONTRACT.md): parameters are only ever added, never changed.
Note
The embed lives at
https://layerhop.com/embedonce the web app is deployed. Until then, runpnpm --filter @layerhop/web devand usehttp://localhost:3000/embed.
Minimal embed#
<iframe
id="layerhop"
src="https://layerhop.com/embed?srcChain=base&srcToken=USDC&dstChain=solana&dstToken=SOL&theme=dark"
title="LayerHop swap"
style="width: 100%; max-width: 440px; height: 640px; border: 0; border-radius: 16px"
allow="clipboard-write"
></iframe>Parameters#
All deep-link swap parameters (srcChain, srcToken, dstChain, dstToken, amount, recipient, slippageBps), plus:
| Param | Example | Meaning |
|---|---|---|
theme |
dark | light |
Default dark. |
accent |
22d3ee |
Accent colour, hex without #. |
radius |
16 |
Card corner radius in px. |
lockSrc / lockDst |
1 |
Lock that side's chain and token (e.g. "deposit into my app on Base"). |
hideHistory |
1 |
Hide the history panel. |
integrator |
myapp |
Attribution label, [a-z0-9-]{1,32}. |
Unknown or invalid parameters are ignored and fall back to defaults; they never break the embed.
Events#
The embed posts messages to the parent window:
window.parent.postMessage({ source: "layerhop", type, payload }, "*");type |
payload |
|---|---|
ready |
{ version } |
quote |
{ provider, srcChain, dstChain, amount, dstAmount } |
submitted |
{ provider, srcChainId, srcTxHash } |
status |
{ provider, srcTxHash, state } — pending / filled / refunded / failed |
error |
{ code, message } |
resize |
{ height } — px; resize the iframe to fit |
Payloads never contain secrets.
Listening safely#
const LAYERHOP_ORIGIN = "https://layerhop.com";
const frame = document.getElementById("layerhop") as HTMLIFrameElement;
window.addEventListener("message", (event) => {
if (event.origin !== LAYERHOP_ORIGIN) return; // production: always check origin
const msg = event.data;
if (!msg || msg.source !== "layerhop") return;
switch (msg.type) {
case "resize":
frame.style.height = `${msg.payload.height}px`;
break;
case "submitted":
analytics.track("swap_submitted", msg.payload);
break;
case "status":
if (msg.payload.state === "filled") showToast("Swap complete");
break;
case "error":
console.warn("LayerHop", msg.payload.code, msg.payload.message);
break;
}
});error.code uses the same error codes as the API.
Example: deposit into your app on Base#
<iframe
src="https://layerhop.com/embed?dstChain=base&dstToken=USDC&lockDst=1&recipient=0xYourDepositAddress&integrator=myapp&accent=bef264"
title="Deposit with LayerHop"
style="width: 100%; max-width: 440px; height: 640px; border: 0"
></iframe>The user can pay from any supported chain and token; the output always lands as USDC on Base at your address. A recipient that differs from the connected wallet is shown with a warning, and remains editable.
@layerhop/widget package#
The package wraps the iframe, validates options, filters messages by origin and auto-resizes. It is not yet published to npm; use it from the monorepo (packages/widget) until then.
React
import { LayerHopWidget } from "@layerhop/widget/react";
<LayerHopWidget
srcChain="base"
dstChain="solana"
dstToken="SOL"
theme="dark"
accent="22d3ee"
onSubmitted={(e) => console.log("submitted", e.srcTxHash)}
onStatus={(e) => console.log(e.state)}
/>;Vanilla JS
import { mount } from "@layerhop/widget";
const widget = mount("#swap", { dstChain: "base", dstToken: "USDC", lockDst: true, onQuote: console.log });
// widget.update({ theme: "light" }); widget.destroy();Script tag — dist/embed.js exposes window.LayerHop.mount and auto-mounts elements:
<div data-layerhop-widget data-src-chain="base" data-dst-chain="solana" data-theme="dark"></div>
<script src="/path/to/embed.js"></script>Each auto-mounted element also re-dispatches events as bubbling layerhop:<type> DOM events (payload in event.detail).
Coming soon
npm release of
@layerhop/widget, and integrator fees on top of LayerHop's fee.