# Implementation guide

Source: https://www.solscanner.app/docs/embed/implementation

> Iframe setup, React component, and common patterns.

Basic iframe [#basic-iframe]

```html
<iframe
  src="https://www.scanner.net/sol/scanner/TOKEN_MINT/map?embed=1&key=YOUR_API_KEY"
  width="100%"
  height="600"
  sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
  allow="clipboard-write"
  style="border: none; border-radius: 12px;"
></iframe>
```

Replace `TOKEN_MINT` and `YOUR_API_KEY`. Host is `https://www.scanner.net` (the apex `scanner.net` redirects to `www`).

For Robinhood Chain, use `/rh/scanner/0xADDRESS/map` instead of `/sol/scanner/TOKEN_MINT/map`. The key, domain settings, and iframe attributes stay the same. `holders`, `quick`, `deep`, and `max` work on both chains; `traders` is Solana-only.

Required `sandbox` tokens [#required-sandbox-tokens]

If your page applies a strict `sandbox` attribute, it must include at least:

| Token                            | Why                                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------------------ |
| `allow-scripts`                  | The map is client-rendered React and requires JS to run                                    |
| `allow-same-origin`              | Preserves the iframe's origin for API requests; without it a sandbox uses an opaque origin |
| `allow-popups`                   | External explorer links open in new tabs                                                   |
| `allow-popups-to-escape-sandbox` | Lets those tabs open without inheriting the iframe sandbox                                 |

Keep `allow-same-origin`. Without it the iframe gets an opaque origin, so its API calls send `Origin: null`, and the backend falls back to the `Referer` header to identify the caller. If your page or a browser setting also strips the referrer, those calls carry no usable origin, and an org with allowed domains rejects them with `403 EMBED_DOMAIN_REJECTED`.

React component [#react-component]

```tsx
function ScannerEmbed({
  address,
  chain = "sol",
  apiKey,
  mode,
  height = 600,
  className,
}: {
  address: string;
  chain?: "sol" | "rh";
  apiKey: string;
  mode?: "holders" | "traders" | "quick" | "deep" | "max";
  height?: number | string;
  className?: string;
}) {
  const params = new URLSearchParams({ embed: "1", key: apiKey });
  if (mode) params.set("mode", mode);
  const src = `https://www.scanner.net/${chain}/scanner/${encodeURIComponent(address)}/map?${params}`;

  return (
    <iframe
      title={`${chain === "rh" ? "Robinhood Chain" : "Solana"} map`}
      src={src}
      width="100%"
      height={height}
      sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
      allow="clipboard-write"
      className={className}
      style={{ border: "none", borderRadius: 12 }}
    />
  );
}
```

```tsx
<ScannerEmbed
  address="So11111111111111111111111111111111111111112"
  apiKey="emb_your_key_here"
  mode="holders"
/>
```

Pass `chain="rh"` and a Robinhood Chain `0x` address to use the same component there. Do not pass `mode="traders"` for Robinhood Chain; that mode shows an unsupported-mode message.

Wallet scans [#wallet-scans]

Use a wallet address instead of a token mint. Depth is optional. If omitted, the embed runs a `quick` scan (same as passing `&quick`).

```html
<iframe
  src="https://www.scanner.net/sol/scanner/WALLET_ADDRESS/map?embed=1&key=YOUR_KEY&deep"
  width="100%" height="600"
  sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
  style="border: none;"
></iframe>
```

| Param             | Speed  | What you get      |
| ----------------- | ------ | ----------------- |
| `quick` (default) | Fast   | Basic connections |
| `deep`            | Medium | Full graph        |
| `max`             | Slow   | Maximum depth     |

Switching tokens dynamically [#switching-tokens-dynamically]

Update the iframe `src` to load a different address without reloading the page:

```javascript
function loadToken(address, chain = "sol") {
  const params = new URLSearchParams({ embed: "1", key: API_KEY });
  document.getElementById("scanner-embed").src =
    `https://www.scanner.net/${chain}/scanner/${encodeURIComponent(address)}/map?${params}`;
}
```

Responsive sizing [#responsive-sizing]

For fluid layouts, use the aspect-ratio trick:

```html
<div style="position: relative; width: 100%; padding-bottom: 56.25%;">
  <iframe
    src="https://www.scanner.net/sol/scanner/TOKEN/map?embed=1&key=KEY"
    style="position: absolute; inset: 0; width: 100%; height: 100%; border: none; border-radius: 12px;"
    sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
    allow="clipboard-write"
  ></iframe>
</div>
```

CSP headers [#csp-headers]

If your site uses `Content-Security-Policy`, allow our host. Include both the apex and `www` so clients following the redirect still pass:

```
Content-Security-Policy: frame-src 'self' https://www.scanner.net https://scanner.net;
```

What's included [#whats-included]

Both chains include interactive maps with zoom, pan, drag, tooltips, node details, and filters. Robinhood's **Scan this wallet** action keeps navigation inside the embedded map with the same key. Solana additionally includes its cluster sidebar and rescan controls (a repeat scan of the same address inside 20 seconds is refused by the server cooldown).

It does not include Scanner navigation, login prompts, or plan upgrade UI.
