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

# Deposit modal

> Configure the deposit modal for different wallet connection modes and deposit flows.

The `DepositModal` component handles the full deposit flow: funding method, source chain and token selection, amount input, and cross-chain bridging.

## Where the funds go

`recipient` (required) is the address that receives the bridged funds on the target chain.

The deposit account is [service-managed](#account-setup) and derived from `(recipient, targetChain, targetToken)`, so it doesn't depend on which wallet the user pays from — or on a wallet existing at all. Changing the target changes the deposit address.

## Connecting a wallet

Three options. The wallet is optional in all of them.

### The modal connects one

The user connects their own wallet via Reown (WalletConnect). The modal manages the connection UI internally. Use this when your app has no wallet infrastructure of its own.

```tsx theme={null}
import { DepositModal } from "@rhinestone/deposit-modal";
import "@rhinestone/deposit-modal/styles.css";

<DepositModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  targetChain={8453}
  targetToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  recipient="0xYOUR_RECIPIENT_ADDRESS"
  backendUrl={process.env.NEXT_PUBLIC_DEPOSIT_PROXY_URL}
  reownAppId="YOUR_REOWN_PROJECT_ID"
  onLifecycle={(event) => event.type === "complete" && console.log(event)}
/>
```

### You supply one

Your app already has a wallet connected (via Privy, Dynamic, Turnkey, wagmi, or anything else). Pass its viem `walletClient` and the modal reuses that session instead of opening its own connect step.

The modal reads the address off `walletClient.account`, so it cannot disagree with what your app thinks the user is connected as.

```tsx theme={null}
import { DepositModal } from "@rhinestone/deposit-modal";
import "@rhinestone/deposit-modal/styles.css";

<DepositModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  targetChain={8453}
  targetToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  recipient="0xYOUR_RECIPIENT_ADDRESS"
  backendUrl={process.env.NEXT_PUBLIC_DEPOSIT_PROXY_URL}
  walletClient={walletClient}
  publicClient={publicClient}
  onLifecycle={(event) => event.type === "complete" && console.log(event)}
/>
```

Pass `onRequestConnect` as well if your app needs to run a login flow when the user picks the wallet row before one is connected.

To disconnect a wallet the modal connected, call the exported `disconnectWallet()`. It no-ops with a warning if `@reown/appkit` isn't installed.

<Tip>
  `walletClient={undefined}` is not the same as omitting the prop. Passing it while your
  wallet connects tells the modal one is coming, so it waits instead of deciding there
  is none.
</Tip>

### No wallet at all

QR transfer, the fiat on-ramp and exchange connect need no wallet. Supply neither `walletClient` nor `reownAppId` and the modal opens straight into whichever [funding methods](#funding-methods) you enabled.

```tsx theme={null}
import { DepositModal } from "@rhinestone/deposit-modal";
import "@rhinestone/deposit-modal/styles.css";

<DepositModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  targetChain={8453}
  targetToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  recipient="0xYOUR_RECIPIENT_ADDRESS"
  backendUrl={process.env.NEXT_PUBLIC_DEPOSIT_PROXY_URL}
  enableQrTransfer
  onLifecycle={(event) => event.type === "complete" && console.log(event)}
/>
```

## Funding methods

Each method is a row on the modal's home screen. When exactly one is enabled there is nothing to choose, and the modal opens directly into it.

| Prop                    | Type                    | Default  | Description                                                                                                                           |
| ----------------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `enableWallet`          | `boolean`               | `true`   | Offer the connected wallet as a source. Turn off for a flow with no wallet even when a `walletClient` or `reownAppId` is supplied     |
| `enableQrTransfer`      | `boolean`               | `true`   | Offer the "Transfer crypto" row — a deposit address and QR code the user sends to from anywhere                                       |
| `enableFiatOnramp`      | `boolean`               | `false`  | Offer card / bank / Apple Pay payment via Swapped's embedded iframe                                                                   |
| `fiatMethods`           | `FiatMethodsConfig`     | regional | Restrict which Swapped payment groups the on-ramp offers. Omitting it uses [regional personalization](#regional-payment-methods)      |
| `enableExchangeConnect` | `boolean`               | `false`  | Offer "Connect exchange" — the user picks their CEX inside Swapped's iframe and withdraws from it                                     |
| `assetMigrations`       | `AssetMigrationsConfig` | none     | Offer [migrating balances or DeFi positions](/deposits/widget/asset-migrations) the user already holds in a supported third-party app |

`enableFiatOnramp` and `enableExchangeConnect` require Swapped keys on your backend.

### Regional payment methods

Leave `fiatMethods` unset and the modal personalizes the Cash options for the user's region. It renders the method list the backend resolves — provider labels, icons, limits, and one optional **Popular** badge — and hands the exact method the user picked to the signed Swapped widget URL.

This is the recommended default: which methods exist varies by country, and a hard-coded subset can only go stale.

Personalization never blocks the flow. After 500 ms the standard card, bank transfer and Apple Pay rows render; a late regional result only replaces a picker the user hasn't touched; and any failure keeps the complete default set. Resolving the country happens at your proxy — see [regional payment methods](/deposits/widget/backend#regional-payment-methods) for what it needs.

<Note>
  A known country with **no** available methods is authoritative merchant data, and the
  modal hides the Cash option entirely. That is different from an unresolved country,
  which renders the defaults.
</Note>

### Restricting fiat payment methods

`fiatMethods` is a boolean map keyed by Swapped `payment_group`. Passing it pins the Cash rows to that exact subset and opts this modal instance out of regional personalization.

```tsx theme={null}
<DepositModal
  // ...required props
  enableFiatOnramp
  fiatMethods={{ creditcard: true, "apple-pay": true }}
/>
```

Valid keys are `creditcard`, `bank-transfer`, and `apple-pay`. Enabling one does not advertise the others.

<Warning>
  An empty or all-false `fiatMethods` offers **no** payment methods, not all of them.
  To offer everything, omit the prop — which also turns personalization back on.
</Warning>

## Transfer configuration

Control the deposit destination and optionally pre-fill source parameters.

| Prop               | Type                          | Required | Description                                                                                                                          |
| ------------------ | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `targetChain`      | `Chain \| number \| "solana"` | Yes      | Destination chain (viem `Chain` object, chain ID, or `"solana"`)                                                                     |
| `targetToken`      | `Address \| string`           | Yes      | Token address on the destination chain (base58 mint for Solana)                                                                      |
| `recipient`        | `Address \| string`           | Yes      | Where funds are delivered on the target chain (base58 address for Solana)                                                            |
| `defaultAmount`    | `string`                      | No       | Pre-filled deposit amount. A USD number string (e.g. `"25"`) or the sentinel `"max"` to fill the full available balance.             |
| `sourceChain`      | `Chain \| number`             | No       | Pre-selected source chain                                                                                                            |
| `sourceToken`      | `Address`                     | No       | Pre-selected source token                                                                                                            |
| `outputTokenRules` | `OutputTokenRule[]`           | No       | Route the deposit to a different final token based on what the user deposited                                                        |
| `rejectUnmapped`   | `boolean`                     | No       | Reject deposits that don't match any routing rule instead of falling back to `targetToken`                                           |
| `appBalanceUsd`    | `number`                      | No       | The user's current in-app balance (USD). When set, the amount screen shows a "Balance after deposit" row (`appBalanceUsd + amount`). |

For supported chains and tokens, see [supported chains](/deposits/overview#supported-chains-and-tokens).

### HyperCore destinations

HyperCore is `targetChain: 1337` (exported as `HYPERCORE_CHAIN_ID`) and accepts USDC only. The `recipient` can be an EOA or a smart account: deposits are credited through the MulticallHandler's `depositFor(recipient)`, which funds any address's HyperCore account identically and never executes on the recipient.

<Note>
  Earlier versions pre-screened the recipient's bytecode and blocked a contract with
  an `onError` code of `HYPERCORE_RECIPIENT_NOT_EOA`. That rule never matched the
  orchestrator and is gone as of v0.9.0 — the code is no longer emitted.
</Note>

HyperCore is also available as a deposit **source** in the QR / transfer flow, using the account's own EVM address as the deposit address; a native Hyperliquid L1 spot transfer lands there.

### Restricting which sources you accept

Which chains and tokens you accept is your project's **deposit whitelist**, set via `POST /setup` and enforced by the processor when a deposit arrives. Configure it there rather than in the modal. To offer a restricted subset, use an API key whose whitelist matches.

<Warning>
  Filtering the pickers client-side instead offers the user a source the processor then
  rejects, as soon as the two lists drift apart. The whitelist is the only place the
  restriction is enforced.
</Warning>

## Account setup

The modal uses **service-managed accounts**: the deposit account is owned by
Rhinestone and settles to your `recipient`, so there is no session key to configure
and the user is never asked to sign during setup.

| Prop            | Type      | Default | Description                                                                                              |
| --------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------- |
| `forceRegister` | `boolean` | `false` | Re-register even if the account already exists (bypasses the local cache; the call itself is idempotent) |

## Destination token routing

Deliver a different final token depending on what the user deposits. Pass `outputTokenRules` to map source deposits — matched by chain, token address, or symbol — to the output token delivered on the target chain. Deposits that don't match any rule fall back to `targetToken`, or are rejected when `rejectUnmapped` is `true`.

```tsx theme={null}
<DepositModal
  // ...required props
  targetToken="0xFALLBACK_TOKEN_ADDRESS"
  outputTokenRules={[
    {
      match: { symbol: "USDC" },
      outputToken: "0x7f5c764cbc14f9669b88837ca1490cca17c31607",
    },
    {
      match: { symbol: "ETH" },
      outputToken: "0x4200000000000000000000000000000000000006",
    },
  ]}
/>
```

When several rules match the same deposit, the most specific one wins: `chain + token` outranks `chain + symbol`, which outranks `token`, then `symbol`, then `chain` alone. See [token routing](/deposits/api/account-registration#optional-token-routing) for the full rule semantics, priority order, and additional examples.

## Migrate assets from other apps

Fund a deposit from balances the user already holds in a supported third-party app,
with no manual transfer first. Opt in per provider via `assetMigrations`.

See [asset migrations](/deposits/widget/asset-migrations) for the provider list,
Polymarket specifics, and the headless account lookup.

## Package entry points

Everything is on the root entry; the subpaths exist so you only bundle what you use.

| Import                                 | Contains                                                                                      |
| -------------------------------------- | --------------------------------------------------------------------------------------------- |
| `@rhinestone/deposit-modal`            | All three modals, types, chain and token helpers                                              |
| `@rhinestone/deposit-modal/deposit`    | `DepositModal` and its types                                                                  |
| `@rhinestone/deposit-modal/withdraw`   | `WithdrawModal` and its types                                                                 |
| `@rhinestone/deposit-modal/claim`      | [`ClaimModal`](/deposits/widget/claim-modal) and its types                                    |
| `@rhinestone/deposit-modal/server`     | `createRefundHandler`. **Server-only** — holds your API key                                   |
| `@rhinestone/deposit-modal/constants`  | `MODAL_VERSION`, chain registry, token and explorer helpers                                   |
| `@rhinestone/deposit-modal/polymarket` | [`getPolymarketAccount`](/deposits/widget/asset-migrations#headless-account-lookup), headless |
| `@rhinestone/deposit-modal/styles.css` | Stylesheet, required                                                                          |

## Display modes

By default, the component renders as a centered modal overlay with a backdrop. Set `inline={true}` to render it without the overlay, fitting into your page layout.

```tsx theme={null}
<DepositModal
  isOpen={true}
  onClose={() => {}}
  inline={true}
  // ...other props
/>
```

Set `closeOnOverlayClick={false}` to prevent the modal from closing when the user clicks outside it.

## Props reference

### Required

| Prop          | Type                          | Description                                                              |
| ------------- | ----------------------------- | ------------------------------------------------------------------------ |
| `isOpen`      | `boolean`                     | Controls modal visibility                                                |
| `onClose`     | `() => void`                  | Called when the user closes the modal                                    |
| `targetChain` | `Chain \| number \| "solana"` | Destination chain (viem `Chain` object, chain ID, or `"solana"`)         |
| `targetToken` | `Address \| string`           | Token address on the destination chain                                   |
| `recipient`   | `Address \| string`           | Where funds are delivered on the target chain                            |
| `backendUrl`  | `string`                      | Your [backend proxy](/deposits/widget/backend), which holds your API key |

### Wallet

| Prop               | Type                   | Default | Description                                                                                                |
| ------------------ | ---------------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `reownAppId`       | `string`               | —       | Reown project ID. Lets the modal connect a wallet itself.                                                  |
| `walletClient`     | `WalletClient \| null` | —       | A wallet your app already has connected. The modal reuses the session and reads the address off `.account` |
| `publicClient`     | `PublicClient \| null` | —       | Read client paired with `walletClient`. Defaults to the modal's own                                        |
| `enableWallet`     | `boolean`              | `true`  | Offer the connected wallet as a funding source at all                                                      |
| `onRequestConnect` | `() => void`           | —       | Called when the modal needs the user to connect a wallet                                                   |

### Transfer

| Prop               | Type                | Default | Description                                                           |
| ------------------ | ------------------- | ------- | --------------------------------------------------------------------- |
| `defaultAmount`    | `string`            | —       | Pre-filled deposit amount. USD number string or the sentinel `"max"`. |
| `sourceChain`      | `Chain \| number`   | —       | Pre-selected source chain                                             |
| `sourceToken`      | `Address`           | —       | Pre-selected source token                                             |
| `appBalanceUsd`    | `number`            | —       | In-app USD balance; enables the "Balance after deposit" row           |
| `outputTokenRules` | `OutputTokenRule[]` | —       | Per-deposit output token routing rules                                |
| `rejectUnmapped`   | `boolean`           | `false` | Reject deposits that don't match any `outputTokenRules` entry         |

### Funding

| Prop                    | Type                          | Default  | Description                                                                                                                                                                             |
| ----------------------- | ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enableWallet`          | `boolean`                     | `true`   | [Offer the connected wallet](#funding-methods) as a source                                                                                                                              |
| `enableQrTransfer`      | `boolean`                     | `true`   | Offer the "Transfer crypto" deposit-address / QR row                                                                                                                                    |
| `enableFiatOnramp`      | `boolean`                     | `false`  | Offer fiat payment via Swapped's iframe. Requires backend Swapped keys                                                                                                                  |
| `fiatMethods`           | `FiatMethodsConfig`           | regional | [Restrict payment groups](#restricting-fiat-payment-methods) — `{ creditcard?, "bank-transfer"?, "apple-pay"? }`. Empty means none; omitted means [regional](#regional-payment-methods) |
| `enableExchangeConnect` | `boolean`                     | `false`  | Offer "Connect exchange". Requires backend Swapped keys                                                                                                                                 |
| `assetMigrations`       | `AssetMigrationsConfig`       | —        | [Migrate balances or positions](/deposits/widget/asset-migrations) from third-party apps (e.g. `{ polymarket: true, aave: true }`)                                                      |
| `initialAssetMigration` | `keyof AssetMigrationsConfig` | —        | Open the modal pre-routed into a migration provider (e.g. `"aave"`), skipping the home screen. Must name an enabled `assetMigrations` key.                                              |

### Account

| Prop            | Type      | Default | Description                                    |
| --------------- | --------- | ------- | ---------------------------------------------- |
| `forceRegister` | `boolean` | `false` | Re-register even if the account already exists |

### Backend

| Prop      | Type        | Default | Description                                                                                                                                                                                                                                                        |
| --------- | ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rpcUrls` | `RpcUrlMap` | —       | Per-chain RPC overrides keyed by EVM chain id or the literal `"solana"` key (e.g. `{ 8453: "https://…", solana: "https://…" }`). Applied to EVM public clients, the connected wallet, HyperEVM, and the Solana connection; chains left unset use their default RPC |

### Display

| Prop                  | Type                   | Default | Description                                                         |
| --------------------- | ---------------------- | ------- | ------------------------------------------------------------------- |
| `inline`              | `boolean`              | `false` | Render without modal overlay                                        |
| `closeOnOverlayClick` | `boolean`              | `true`  | Close modal on backdrop click                                       |
| `className`           | `string`               | —       | CSS class for the modal container                                   |
| `theme`               | `DepositModalTheme`    | —       | [Theme configuration](/deposits/widget/customization#theme)         |
| `uiConfig`            | `DepositModalUIConfig` | —       | [UI configuration](/deposits/widget/customization#ui-configuration) |
| `debug`               | `boolean`              | `false` | Enable debug logging                                                |

### Callbacks

| Prop          | Type                                     | Description                                                                                                                                                                           |
| ------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onReady`     | `() => void`                             | Modal initialized                                                                                                                                                                     |
| `onLifecycle` | `(event: DepositLifecycleEvent) => void` | [Lifecycle event](/deposits/widget/status-tracking#onlifecycle) — switch on `event.type` (`connected`, `submitted`, `complete`, `failed`, `balance-changed`, `smart-account-changed`) |
| `onError`     | `(data: ErrorEventData) => void`         | Error at any stage                                                                                                                                                                    |
| `onEvent`     | `(event: DepositAnalyticsEvent) => void` | [Analytics event](/deposits/widget/status-tracking#analytics)                                                                                                                         |

See [status tracking](/deposits/widget/status-tracking) for lifecycle event payloads.

## Content security policy

The modal loads chain, token and exchange logos from Rhinestone's asset CDN. **If your app sets an explicit `img-src` policy, it must allow the CDN:**

```
img-src 'self' data: https://s3.rhinestone.dev;
```

<Warning>
  A blocked image fails **silently** — the icon renders blank with no console error
  naming the policy, so this is easy to misread as a modal bug. Apps with no explicit
  `img-src` (or `img-src *`) need no change.
</Warning>

If you enable the [fiat on-ramp or exchange connect](#funding-methods), the modal embeds Swapped's widget in an iframe, which needs `frame-src`:

```
frame-src https://widget.swapped.com https://sandbox.swapped.com https://connect.swapped.com;
```
