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

> ## Agent Instructions
> Before reading these docs, install the Circle Skill for Arc to get pre-loaded context: use-arc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) — covers chain config, RPC setup, contract deployment, USDC bridging, and gas with USDC. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills.
> USDC is the gas token. Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions.
> Read EVM differences before writing Arc code. Arc targets the Osaka EVM baseline. Most contracts deploy unchanged, but several runtime behaviors diverge from Ethereum: USDC uses 18 decimals natively (not 6), the system emitter `0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE` logs all USDC Transfer events, the mempool enforces a 20 Gwei `maxFeePerGas` floor, blocklist reverts consume gas without a receipt, and `address(0)` sends revert rather than succeed. https://docs.arc.io/arc/references/evm-differences.md is the canonical reference for all Arc-specific runtime behaviors. Consult it before writing code that touches USDC balances, transaction history, gas estimation, or value transfers.
> Sub-second finality. Transactions are final in under 1 second. No need to wait for multiple block confirmations.
> Use App Kits for multichain payment and liquidity capabilities. App Kits wraps CCTP and provides Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, and Borrow Kit. Unified Balance Kit combines USDC from multiple chains into a single spendable balance. Earn Kit integrates earn opportunities into your app. Borrow Kit lets users borrow USDC against cirBTC collateral on Arc.
> Use Arc Foundry for contract development. Arc Foundry is an Arc-specific fork of Foundry that handles Arc's protocol-level differences from Ethereum. It provides arc-forge, arc-cast, and arc-anvil. Install it before deploying contracts: https://docs.arc.io/arc/tutorials/install-arc-foundry.md.
> Arc is available on both Testnet and Mainnet. See https://docs.arc.io/arc/references/connect-to-arc.md for RPC endpoints and https://faucet.circle.com for testnet tokens.
> Always check Contract Addresses: https://docs.arc.io/arc/references/contract-addresses.md
> Building beyond Arc? Circle offers skills for the full platform: use-usdc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md), use-circle-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md), use-developer-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md), use-user-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md), use-modular-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md), use-gateway (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md), use-smart-contract-platform (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md). Full Circle developer docs: https://developers.circle.com/llms.txt.

# Onramp hosting requirements

> Page-level constraints, Content Security Policy directives, container sizing rules, and webhook obligations for hosting the Onramp widget.

The Onramp widget renders inside a cross-origin iframe or popup served from a
hosted origin on Arc. Configure your host page to meet the requirements on this
page before deploying. For an end-to-end walkthrough that uses these settings,
see the [embed widget quickstart](/app-kit/quickstarts/onramp-embed-widget).

## Endpoints

Onramp exposes two environments. Use sandbox for local development and testing.
Use production for real transactions.

| Endpoint | Sandbox | Production |
| - | - | - |
| Widget | `https://onramp-sandbox.arc.io` | `https://onramp.arc.io` |
| API | `https://api-test.circle.com` | `https://api.circle.com` |

Your [API key](/app-kit/onramp#api-key) authenticates your server. Keep it
server-side only, since it grants full access to your Onramp integration.

### Widget origin

Both `createAppServerKit` on your server and `new AppKit` on your client accept
a `widgetBaseUrl` option that points at the widget origin. The default is the
production widget URL, so production integrations don't need to set it. To
target sandbox, pass the sandbox widget origin on both sides.

Server side:

```typescript theme={null}
const server = createAppServerKit({
  onramp: {
    apiKey: process.env.CIRCLE_API_KEY!,
    widgetBaseUrl: "https://onramp-sandbox.arc.io",
  },
});
```

Client side:

```typescript theme={null}
const kit = new AppKit({
  onramp: {
    widgetBaseUrl: "https://onramp-sandbox.arc.io",
  },
});
```

The server and client values must match. If they differ, `mountIframe` and
`openWindow` throw an
[`INPUT_WIDGET_URL_ORIGIN_MISMATCH`](/app-kit/references/onramp-error-handling)
error (code 1910).

## Sandbox

Sandbox is a testing environment for developing your Onramp integration without
real money movement. It exposes the same API surface as production. Only the
endpoints and the API key binding differ.

Use sandbox for local development. The production widget's Content Security
Policy blocks `localhost`, so a production widget won't load from a local dev
server.

To target sandbox, override two config values on the server and one on the
client. API keys are environment-bound. A sandbox API key won't work against
production, and a production API key won't work against sandbox.

| Setting | Set on | Sandbox | Production |
| - | - | - | - |
| `apiKey` | server (`createAppServerKit`) | your sandbox API key | your production API key |
| `baseUrl` | server (`createAppServerKit`) | `https://api-test.circle.com` | `https://api.circle.com` (default) |
| `widgetBaseUrl` | server and client | `https://onramp-sandbox.arc.io` | `https://onramp.arc.io` (default) |

### Example `.env`

```bash .env theme={null}
# --- server-only secret ---
CIRCLE_API_KEY=YOUR_API_KEY

# --- sandbox ---
ONRAMP_API_BASE_URL=https://api-test.circle.com
NEXT_PUBLIC_ONRAMP_WIDGET_BASE_URL=https://onramp-sandbox.arc.io

# --- production ---
# ONRAMP_API_BASE_URL=https://api.circle.com
# NEXT_PUBLIC_ONRAMP_WIDGET_BASE_URL=https://onramp.arc.io
```

The `widgetBaseUrl` value is safe to expose to the browser, so a `NEXT_PUBLIC_`
(or your framework's equivalent) prefix lets the client read it. The API key is
the only real secret and never gets a public prefix.

### Testing behavior

Sandbox doesn't move real money. It sends testnet tokens instead. Use dummy data
only. Never enter real PII.

| Area | Sandbox behavior |
| - | - |
| Phone OTP | No SMS is sent. Any six-digit code passes. `000000` fails and keeps the challenge open for retries. Country prefixes such as `+44` need no special setup. |
| KYC identity | Photo verification is required, but doesn't require a real ID. Any photo containing a human face passes. |
| Debit card | Don't enter real card details. |
| Apple Pay / Google Pay | Follow the widget's instructions. Your card isn't charged. |
| Bank transfers | The bank transfer details page has a button to submit a test deposit. Test funds land in the destination wallet only on Ethereum. |
| Deposit events | Only `DEPOSIT_SUBMITTED` fires, with `settlementExpected: false`. `DEPOSIT_SETTLED` is defined in the SDK but isn't emitted in sandbox. To verify a test deposit, check the destination wallet's USDC balance on the configured blockchain's block explorer. |

The identity form's required fields vary by region:

| Region | Required fields |
| - | - |
| US | First name, last name, date of birth, email, address, state, ZIP code, 9-digit SSN. |
| Non-US | First name, last name, date of birth, email, address with a valid formatted postcode for the country, and a tax or national identifier (any letters or digits are accepted). A state or region is only required when the country lists one. |

## Content security policy

If your site sends a CSP header, you must allow the widget and API origins for
the environment you target. Without these directives, the iframe silently fails
to load.

Sandbox:

```text theme={null}
frame-src https://onramp-sandbox.arc.io;
connect-src https://onramp-sandbox.arc.io https://api-test.circle.com;
```

Production:

```text theme={null}
frame-src https://onramp.arc.io;
connect-src https://onramp.arc.io https://api.circle.com;
```

## Container element

Two container-side preconditions apply when calling `mountIframe`:

### The container must be attached to the document

The App Kit SDK appends the iframe synchronously and installs a
`MutationObserver` that auto-disposes the widget if the container later
detaches. The container must be in the DOM when you call `mountIframe`:

* In React, mount from a `useEffect` (not during render) so the ref is populated
  before the call.
* In other frameworks, use that framework's mounted lifecycle.
* In plain browser code, wait for `DOMContentLoaded` or insert the container
  yourself before calling.

### The container must have an explicit, non-zero height

The iframe renders at `width: 100%; height: 100%`. A cross-origin iframe cannot
size itself to its content, so without a resolved height the iframe collapses to
0px and the user sees nothing.

```css theme={null}
#onramp-root {
  height: 720px;
}
```

Any of these resolve the height correctly:

* A fixed `height` (such as `720px`).
* A `min-height`.
* A flex child inside a parent with a sized cross-axis.

## Popup mode constraints

When you use `openWindow` instead of `mountIframe`, the host environment
matters:

* **Mobile browsers** open a new tab, not a sized popup. The `width` and
  `height` features are ignored.
* **In-app browsers** (Instagram, Facebook, TikTok, LinkedIn, WeChat, LINE)
  block popups. `openWindow` returns
  `{ status: 'blocked', reason: 'in_app_browser' }` so you can fall back to
  `mountIframe`.
* **Installed web apps in standalone mode** also block popups. `openWindow`
  returns `{ status: 'blocked', reason: 'pwa_standalone' }`.

See [Choose iframe or popup mode](/app-kit/tutorials/onramp/iframe-vs-popup) for
the full fallback flow.

## Third-party storage in iOS Safari

iOS Safari's Intelligent Tracking Prevention (ITP) can restrict the embedded
app's access to its own storage inside an iframe. If you see KYC or session
issues that only occur on iOS Safari, use `openWindow` mode on that platform.

## Webhooks are the source of truth

Browser lifecycle events such as `DEPOSIT_SUBMITTED` and `DEPOSIT_SETTLED` are
best-effort UX signals. A user can close the tab between submitting a deposit
and settlement, and the browser will never deliver the settlement event even
though the deposit completes server-side.

<Warning>
  **Do not treat the absence of `DEPOSIT_SETTLED` as "no deposit."** Reconcile
  final state from webhook events delivered to your backend. Use the in-browser
  events only to drive UI.
</Warning>

## Session lifetime

Onramp sessions are valid for 30 minutes from creation. If your app pre-fetches
or reuses a session, mint a fresh one once more than 30 minutes have passed to
avoid a stale-session error.

## Close unused widgets

Each `mountIframe()` call, and each successful `openWindow()` call, returns a
widget controller. Keep that controller and call `widget.close()` before
starting over or when the user leaves the view.
