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

# Embedding the widget

> Drop-in swap UI in an iframe, using the wallet your page already has connected.

A hosted swap UI you embed in an `<iframe>`. It never asks the user to connect a second wallet:
`@ravn/widget-connector` bridges the iframe to whatever wallet is already connected on your
page, over `postMessage`.

<Steps>
  <Step title="Embed the iframe">
    Point `src` at `/widget/v1`, with `?origin=` set to your page's **exact** origin. There is
    no fallback or guessing on RAVN's side, so the param is required.

    ```html theme={null}
    <iframe
      src="https://app.ravn.exchange/widget/v1?origin=https://your-site.example"
      sandbox="allow-scripts allow-same-origin allow-forms"
    ></iframe>
    ```

    The version segment is part of the contract. A future breaking change ships as `/widget/v2`,
    so an already-embedded `v1` iframe keeps working unchanged. `/widget` with no version always
    redirects to the latest, which is handy for manual testing; don't embed that one in
    production.

    <Warning>
      **Keep `allow-same-origin`.** The bridge's trust model depends on the iframe reporting its
      real origin on every message it sends. Both sides check `event.origin` against a pinned,
      exact value, never a wildcard. Dropping `allow-same-origin` gives the iframe an opaque
      `"null"` origin and breaks that check completely, so the widget will never be able to talk
      to your bridge. Don't add `allow-top-navigation` or `allow-popups` either; the widget never
      navigates or opens a window, and every wallet action is relayed over `postMessage`.
    </Warning>
  </Step>

  <Step title="Implement your wallet interfaces">
    `@ravn/widget-connector` doesn't hold or ask for keys. It calls back into your own
    wallet/signer, the one your page already has connected via wagmi, a Solana adapter, or
    whatever else you're using.

    ```ts theme={null}
    import type { RavnWallet, RavnSolanaWallet } from "@ravn/widget-connector";

    const evmWallet: RavnWallet = {
      address: account.address ?? null,
      chainId: account.chainId ?? null,
      sendTransaction: (req) => wagmiSendTransaction(req),
      signTypedData: (req) => wagmiSignTypedData(req.typedData),
      signMessage: (req) => wagmiSignMessage(req.message),
      switchChain: (req) => wagmiSwitchChain(req.chainId).then(() => ({ ok: true })),
    };

    const solanaWallet: RavnSolanaWallet = {
      address: solanaAccount?.address ?? null,
      signTransaction: (req) => adapter.signTransaction(req.transaction),
    };
    ```

    <Warning>
      These methods are **your** wallet-signing boundary, not RAVN's. Implement them by calling
      your existing wallet/signer, the one that already shows its own confirmation UI before
      signing. Never wire them to an auto-signer with no confirmation step: the widget iframe
      relays whatever it's asked to relay, so your implementation is the only place a user gets to
      see what they're approving.
    </Warning>
  </Step>

  <Step title="Create the bridge">
    ```ts theme={null}
    import { createRavnWidgetBridge } from "@ravn/widget-connector";

    const bridge = createRavnWidgetBridge({
      iframe: iframeRef.current,
      widgetOrigin: "https://app.ravn.exchange",
      getWallet: () => (isEvmConnected ? evmWallet : null),
      getSolanaWallet: () => (isSolanaConnected ? solanaWallet : null),
    });

    // Whenever your EVM wallet state changes (connect, account/chain switch, disconnect):
    bridge.updateWallet(isEvmConnected ? { address, chainId } : null);

    // Independently, whenever your Solana wallet state changes:
    bridge.updateSolanaWallet(isSolanaConnected ? { address: solanaAddress } : null);

    // On unmount:
    bridge.destroy();
    ```

    `getWallet`/`getSolanaWallet` answer ad-hoc requests from the widget, while
    `updateWallet`/`updateSolanaWallet` push state changes to it proactively. EVM and Solana are
    independent: a user can have either, both, or neither connected, and one changing never
    implies anything about the other.
  </Step>
</Steps>

## Why no double wallet-connect

The widget renders its own UI, but never its own wallet-connect flow while the bridge is live.
It sends a `bridge:ready` handshake on load, and if your page answers with a `bridge:init`
(which `createRavnWidgetBridge` does automatically), the widget uses that wallet state instead
of prompting the user to connect one of its own. The widget only falls back to a self-contained
connect UI if the bridge never responds at all, for example if it's embedded without
`@ravn/widget-connector` wired up on the host side.

## CSP and framing

* A CSP `frame-src` restricted to `https://app.ravn.exchange`, rather than `*`, on your own page
  is good hygiene: it stops your page from ever framing anything else under that directive by
  accident.
* RAVN's `/widget/*` route intentionally sends no `X-Frame-Options` or `frame-ancestors`
  restriction, since the whole point is that any integrator can embed it. Origin trust is
  enforced entirely at the application layer, through the `?origin=` param plus the bridge's
  origin and source checks on every message, not through browser framing headers.

## Reference

### createRavnWidgetBridge(options)

<ParamField body="iframe" type="HTMLIFrameElement" required>The embedded iframe element.</ParamField>
<ParamField body="widgetOrigin" type="string" required>The exact origin the widget is served from, e.g. `https://app.ravn.exchange`. Never `*`.</ParamField>
<ParamField body="getWallet" type="() => RavnWallet | null" required>Called on every `wallet:*` request from the widget. Return `null` while no EVM wallet is connected.</ParamField>
<ParamField body="getSolanaWallet" type="() => RavnSolanaWallet | null">Called on every `solana:*` request. Independent of `getWallet`; return `null` while no Solana wallet is connected.</ParamField>
<ParamField body="config" type="Record<string, unknown>">Non-wallet config sent once at handshake: theme, allowed tokens, an integrator API key, and so on.</ParamField>

Returns a `RavnWidgetBridge`:

<ResponseField name="updateWallet(wallet)" type="(wallet: { address, chainId } | null) => void">Call whenever the host's own EVM wallet state changes: connect, account switch, chain switch, disconnect.</ResponseField>
<ResponseField name="updateSolanaWallet(wallet)" type="(wallet: { address } | null) => void">Call whenever the host's own Solana wallet state changes. Independent of `updateWallet`.</ResponseField>
<ResponseField name="destroy()" type="() => void">Removes the message listener. Call on unmount.</ResponseField>

### Wallet interfaces

<ResponseField name="RavnWallet" type="interface">`address`, `chainId`, and `sendTransaction` / `signTypedData` / `signMessage` / `switchChain`: one method per `wallet:*` action the widget can send.</ResponseField>
<ResponseField name="RavnSolanaWallet" type="interface">`address` and `signTransaction`, given base64 of an **unsigned** `VersionedTransaction`, returning base64 of the signed one.</ResponseField>

An unimplemented or momentarily-null wallet isn't a bridge-level failure. A `wallet:*` or
`solana:*` request while `getWallet()`/`getSolanaWallet()` returns `null` gets back
`WALLET_NOT_CONNECTED` or `SOLANA_WALLET_NOT_CONNECTED`, which surfaces to the widget as a
rejected request, the same as any other failed wallet action.
