LayerHopdocs

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.

Open in ClaudeOpen in ChatGPT

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/embed once the web app is deployed. Until then, run pnpm --filter @layerhop/web dev and use http://localhost:3000/embed.

Minimal embed#

HTML
<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:

TypeScript
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#

TypeScript
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#

HTML
<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

TSX
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

TypeScript
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:

HTML
<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.