> For the complete documentation index, see [llms.txt](https://docs.glacislabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.glacislabs.com/zero-delta/widget/install.md).

# Install & Embed

Four ways onto the page — React, mount(), a script tag, or Next.js — plus the compatibility floor.

```sh
npm i @glacislabs/stablegenie-zerodelta
```

`react` and `react-dom` (>= 18) are **peer dependencies** for the main entry — they are kept external in the ESM and CJS builds, so your React is reused and nothing is double-bundled. The package itself has **zero runtime dependencies**.

> **CSS is auto-injected. There is no stylesheet to import.** The widget writes its own styles into the document on first render, scoped to its own root.

> **You also need the keyless proxy.** The widget's default `apiBase` is a same-origin endpoint you deploy, which injects the ZeroDelta key server-side. Without it every request answers `503 proxy_misconfigured`. This is not optional — see [The Keyless Proxy](/zero-delta/widget/keyless-proxy.md).

### Entry points

| Import                              | Contains                                                                                                  | React required              |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------- |
| `@glacislabs/stablegenie-zerodelta` | the React components, `mount`, the theme helpers, `createEngine`                                          | **yes**                     |
| `…/core`                            | the framework-agnostic engine — quoting, routing, encoding, the pre-sign validator, the on-chain verifier | **no**                      |
| `…/mount`                           | imperative mounting for non-React hosts                                                                   | yes (bundled by your build) |
| `…/standalone`                      | the prebuilt IIFE with React bundled in, for plain `<script>` embeds                                      | no                          |

Use `…/core` if you want the routing and validation engine without pulling in React — it is verifiably React-free.

### React

```tsx
import { StableGenieExchange } from "@glacislabs/stablegenie-zerodelta";

// `provider`: your app's EIP-1193 provider (wagmi connector, window.ethereum…) so the user does not connect twice.
<StableGenieExchange
  variant="wide"
  defaultFromChainId={42161}
  defaultFromAsset="USDT"
  defaultToChainId={8453}
  defaultToAsset="USDC"
  provider={provider}
/>
```

An exact-receive deposit instead:

```tsx
import { StableGeniePay } from "@glacislabs/stablegenie-zerodelta";

// recipient = your deposit address (required — the widget ships no default and never falls back to the payer).
<StableGeniePay
  recipient="0x0000000000000000000000000000000000000000"
  toChainId={42161}
  toAsset="USDC"
  amount="250.00"
  provider={provider}
  onSuccess={({ hash }) => console.log("paid", hash)}
/>
```

### Imperative `mount()`

For React hosts that do not write JSX at the embed point.

```ts
import { mount } from "@glacislabs/stablegenie-zerodelta";

const unmount = mount(document.getElementById("sg"), {
  variant: "exchange",
  defaultFromChainId: 42161,
  defaultFromAsset: "USDT",
  defaultToChainId: 8453,
  defaultToAsset: "USDC",
});
// later: unmount();
```

> **`variant` means two different things, and this is the sharpest footgun in the package.** In `mount()` it selects the **component** — `"pay"` (default), `"swap"`, `"exchange"` or `"rwa"`. In a component it selects the **layout** — `"compact"`, `"wide"` or `"pill"`. `mount()` strips its own selector before rendering, so it **cannot express a layout**: there is no way to get a `pill` or `wide` card through `mount()`. Use JSX, or the script-tag helpers below, which pass the display variant through.

### Plain `<script>`

No React on the host at all. The standalone build bundles its own.

```html
<div id="sg"></div>
<!-- Replace VERSION with the exact release you pin. The standalone build bundles React; self-host it if your CSP disallows third-party scripts. -->
<script src="https://unpkg.com/@glacislabs/stablegenie-zerodelta@VERSION/dist/standalone.global.js"></script>
<script>
  StableGenieWidget.mountExchange(document.getElementById("sg"), {
    variant: "wide",
    defaultFromChainId: 42161,
    defaultFromAsset: "USDT",
    provider: window.ethereum,
  });
</script>
```

`VERSION` is the exact release you pin. The current one is on the package's [npm page](https://www.npmjs.com/package/@glacislabs/stablegenie-zerodelta), and the playground's **Script tag** tab prints this line with it already filled in.

The global exposes five helpers, each taking the same props as the matching React component — including the display `variant` — and returning an unmount function:

| Helper                        | Renders                                     |
| ----------------------------- | ------------------------------------------- |
| `mountPay(el, props)`         | `StableGeniePay`                            |
| `mountSwap(el, props)`        | `StableGenieSwap`                           |
| `mountExchange(el, props)`    | `StableGenieExchange`                       |
| `mountRwaExchange(el, props)` | `StableGenieExchange` with `product: "rwa"` |
| `mountExpress(el, props)`     | `PaymentIsland` — ZeroDelta Link            |

**jsDelivr** works too: `https://cdn.jsdelivr.net/npm/@glacislabs/stablegenie-zerodelta@VERSION/dist/standalone.global.js`

> **Pin an exact version, and prefer self-hosting if your CSP disallows third-party script origins — this widget sits in a payment path.** The file ships inside the package at `dist/standalone.global.js`, so you can copy it into your own static assets at build time:
>
> ```jsonc
> "scripts": {
>   "vendor:widget": "cp node_modules/@glacislabs/stablegenie-zerodelta/dist/standalone.global.js public/vendor/"
> }
> ```

### Next.js and server rendering

The widget is a client component. Importing it on the server does not crash — style injection, `localStorage` and wallet discovery are all guarded on `typeof window` — but it renders nothing useful there.

1. **The package ships no `"use client"` directive.** Wrap it in your own client component:

```tsx
// app/components/Checkout.tsx
"use client";
import { StableGeniePay } from "@glacislabs/stablegenie-zerodelta";

export function Checkout({ amount }: { amount: string }) {
  return <StableGeniePay recipient="0x…" toChainId={42161} toAsset="USDC" amount={amount} />;
}
```

2. **Or skip SSR entirely** where you would rather not ship it in the server bundle:

```tsx
import dynamic from "next/dynamic";
const Checkout = dynamic(() => import("./Checkout").then((m) => m.Checkout), { ssr: false });
```

3. **Route the proxy as a Next handler.** Mount it at `app/api/widget/v1/[...path]/route.ts` so the widget's default `apiBase` resolves without configuration. The proxy logic is plain data in and plain data out — see [The Keyless Proxy](/zero-delta/widget/keyless-proxy.md) for a complete handler.

### Compatibility

|                      |                                                                    |
| -------------------- | ------------------------------------------------------------------ |
| React                | 18 and 19 (peer dependency)                                        |
| Module formats       | ESM + CJS, with `.d.ts` and `.d.cts` types                         |
| Bundlers             | Vite, webpack, Next.js, Rollup, esbuild — nothing special required |
| Node                 | >= 20, for building only                                           |
| Runtime dependencies | none                                                               |
| Licence              | MIT                                                                |

**Browser APIs used:** `fetch`, `BigInt`, `URL` / `URLSearchParams`, `Promise.all`, `CustomEvent`, and `localStorage` (guarded — it degrades cleanly in private or sandboxed contexts). Any browser with those needs no polyfill; there is no `viem` or `ethers` to shim because neither is used.

### When something does not render

| Symptom                                                 | Cause                                                                                                                                                  |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Every request answers `503 proxy_misconfigured`         | The proxy has no `STABLEGENIE_API_KEY` set                                                                                                             |
| `403 origin_not_allowed`                                | Cross-origin embed with the origin missing from `WIDGET_ALLOWED_ORIGINS`                                                                               |
| Card renders but no chains or tokens load               | The `/api/widget/v1/*` route is not mounted or not rewritten to your handler                                                                           |
| **Direct settlement never appears as a route**          | Your proxy does not allow `screen/wallet` — the engine cannot screen, so it fails closed. See [The Keyless Proxy](/zero-delta/widget/keyless-proxy.md) |
| A payment succeeds on-chain but `onSuccess` never fires | Your CSP `connect-src` does not allow the verification RPC — see [The Keyless Proxy](/zero-delta/widget/keyless-proxy.md)                              |
| `variant: "pill"` is ignored                            | You used `mount()`, which cannot express a display variant                                                                                             |

### Try a configuration first

The [widget playground](https://playground.glacislabs.com/) renders every component against the production gateway and prints the exact React, `mount()` and `<script>` snippet for whatever you configure. The snippets on this page are the same ones it generates.
