> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cryptocheckout.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK and embed

> How the embed works, the SRI pin that must be regenerated on every change, and the failures that made it DOA.

## The embed

`public/sdk.js` mounts an iframe pointing at the hosted checkout. The merchant pins their settlement anchor in the init call on their own site:

```html theme={null}
<script src="https://www.cryptocheckout.ai/sdk.js"
        integrity="sha384-…"
        crossorigin="anonymous"></script>
<script>
  CryptoCheckout.init({
    settlementAnchor: "0x…",
    // order details
  });
</script>
```

The anchor is the root of the client-side verification chain. It lives on the merchant's own site, which is precisely why a compromise of the platform's database cannot reach it. See [DB-display-only trust](/trust/db-display-only).

## The SRI pin

<Danger>
  **Any edit to `public/sdk.js` MUST regenerate `SDK_SRI` in `src/lib/sdk/embedSnippet.ts`.** A stale pin makes browsers refuse to execute the SDK entirely — every embed on every merchant site stops working.
</Danger>

```bash theme={null}
openssl dgst -sha384 -binary public/sdk.js | openssl base64 -A
```

The pin is not optional decoration. SRI is what makes the served-code threat *mitigated* rather than unaddressed — without it, a compromise of the served bundle would defeat the entire client-side verification argument.

## Three failures that made the embed DOA

Found in the 2026-07-28 audit. All fixed, all worth understanding because each was invisible in normal testing.

<AccordionGroup>
  <Accordion title="Sandbox without allow-same-origin">
    The iframe was sandboxed without `allow-same-origin`, so the app crashed at boot the moment it touched `localStorage`. The embed had never worked.
  </Accordion>

  <Accordion title="Published snippet used the apex domain">
    The apex 307-redirects to `www`, and the redirect broke the origin check. The snippet must use the canonical `www` host.
  </Accordion>

  <Accordion title="The SDK targeted the wrong checkout">
    `CHECKOUT_PATH` was hardcoded to `/checkout` — the legacy atomic widget — rather than `/pool-checkout`. That silently dropped the pinned anchor, which meant the verification the anchor exists for never ran.
  </Accordion>
</AccordionGroup>

The third is the instructive one: the embed appeared to work, payments went through, and the trust guarantee was quietly absent.

## Bundle budget

<Warning>
  `App.tsx` once statically imported the wallet shell, putting `wallet-vendor` (3.4 MB) and `viem-vendor` into the **entry** chunk. Every route, including the landing page, shipped 5.0 MB with a 4,908 ms first contentful paint.
</Warning>

Now route-aware and lazy via `config/walletRoutes.ts` and `contexts/WalletBootContext.tsx`: **923 KB, 188 ms**.

`scripts/check-bundle-budget.mjs` fails CI if `wallet-vendor` becomes statically reachable from the entry again.

<Danger>
  Do not "fix" a build error by re-adding a static import of `@/config/appkit`. That is the exact regression the budget check exists to catch.
</Danger>

## Content Security Policy

<Warning>
  CSP is set in **two** places — the `vercel.json` HTTP header and the `<meta http-equiv>` tag in `index.html`. When both are present the more restrictive applies. **Patch both or it silently breaks.**
</Warning>

Origins that have broken production before and will again:

| Origin                                | Needed for                |
| ------------------------------------- | ------------------------- |
| `fonts.reown.com`                     | Reown KHTeka fonts        |
| `cca-lite.coinbase.com`               | Coinbase wallet analytics |
| `*.publicnode.com` and per-chain RPCs | Chain reads               |

Third-party FX APIs — `frankfurter.app`, `exchangerate.host`, `open.er-api.com` — are **deliberately not allowlisted**. EUR/USD comes from an on-chain Uniswap V3 pool, never a third-party API.

## The SPA rewrite trap

<Warning>
  The SPA fallback rewrite must exclude `/api/`:

  `/((?!api/|assets|sdk\.js|favicon\.ico|robots\.txt|placeholder\.svg).*)`

  Without that exclusion, Vercel rewrites `/api/quote-uniswap` to `index.html` and the request 404s in a way that looks like an application bug.
</Warning>

## Checkout states

The widget has explicit states for every failure mode rather than a generic error:

| State               | Meaning                                                                   |
| ------------------- | ------------------------------------------------------------------------- |
| `pool_mismatch`     | Verification failed. Signing blocked, address hidden. Terminal.           |
| `recipient_changed` | The on-chain recipient moved between quote and signature.                 |
| `rate_limited`      | Typed 429 with `Retry-After`, driving a 1 Hz countdown that auto-retries. |
| `session_expired`   | A 401 from any function, surfaced as a calm reconnect.                    |

Two resilience behaviours worth knowing:

* **Quote auto-refresh.** A silent re-quote 30 seconds before expiry plus a 3-second "rate updated" pill, so a customer is never surprised by a number that moved while they were about to pay.
* **Closed-tab recovery.** The active payment intent is stamped into local storage on broadcast. On the next mount the widget calls `payment-status`: confirmed goes straight to success, failed clears the key, still-confirming leaves it for the next mount.
