# React modal

The fastest way to add funding is the wallet modal in `@openfort/react`. If you already render `<OpenfortButton />`, your users get a **Deposit** entry point with no extra code: they pick a chain and token, scan a QR or open their wallet, and the funds bridge to their Openfort wallet.

## Drop-in Deposit button

The Deposit flow is part of the connected wallet modal. Once a user has a wallet, the modal exposes **Deposit**, which opens the funding hub:

* **Transfer from wallet** — on mobile, one-tap deeplinks into the user's wallet app (MetaMask, Coinbase Wallet, Phantom, Trust, Rainbow, Rabby) that open a prefilled [deposit send page](/docs/configuration/funding/webview); on desktop, a direct transfer through the connected browser-extension wallet. Deposit-address + QR fallback throughout.
* **Transfer from address** — a cross-chain deposit address with a QR; send from any chain.
* **Transfer from exchange** — Coinbase Onramp, delivering to the destination on a supported chain. Binance is coming soon.
* **Apple Pay** — fiat on-ramp, mobile only (reuses the existing [Buy](/docs/products/embedded-wallet/react/ui) flow).
* **Card** — fiat on-ramp (reuses the existing [Buy](/docs/products/embedded-wallet/react/ui) flow).

```tsx
import { OpenfortButton } from '@openfort/react'

export function App() {
  // The connected wallet modal includes Deposit automatically.
  return <OpenfortButton />
}
```

:::info
Funding settles into the user's active embedded wallet. With zero configuration deposits arrive as **USDC on Base**; override the destination via `uiConfig`:

```tsx
<OpenfortProvider
  publishableKey="pk_…"
  uiConfig={{
    funding: {
      targetChain: 'eip155:137', // deposits land on Polygon…
      targetCurrency: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // …as USDC
    },
  }}
>
```

See [supported chains & currencies](/docs/configuration/funding/headless#supported-chains--currencies) for copy-paste values.
:::

### Overriding the destination address

By default funds land on the **active** embedded wallet address. Set `targetAddress` when that isn't where deposits should go — for example, when your app creates an owner EOA and then deploys a smart account: the active address is the EOA, but you want deposits to fund the smart account.

```tsx
<OpenfortProvider
  publishableKey="pk_…"
  uiConfig={{
    funding: {
      targetChain: 'eip155:8453',
      targetCurrency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
      targetAddress: smartAccountAddress, // fund the smart account, not the active EOA
    },
  }}
>
```

:::warning
`targetAddress` must be a valid address on `targetChain`. For EVM routes it also receives cross-chain **refunds** on the source chain (when a deposit bounces), so prefer an address that resolves to the same value across chains — a `CREATE2` smart account or a plain EOA. Avoid an address that only exists on the destination chain.
:::

### Choosing which methods show

By default the hub shows every available method (Apple Pay first on mobile). Use `funding.methods` to pick which appear and in what order — the same pattern as `authProviders` for the auth modal.

```tsx
import { OpenfortProvider, FundingMethod } from '@openfort/react'

<OpenfortProvider
  publishableKey="pk_…"
  uiConfig={{
    funding: {
      // Only these, in this order — e.g. crypto-only, no fiat.
      methods: [FundingMethod.WALLET, FundingMethod.ADDRESS, FundingMethod.EXCHANGE],
    },
  }}
>
```

`FundingMethod` values: `APPLE_PAY`, `CARD`, `WALLET` (Transfer from wallet), `ADDRESS` (Transfer from address), `EXCHANGE` (Transfer from exchange). Device, region, and availability gating still apply — Apple Pay stays mobile-only, and the cross-chain rails are hidden until the funding backend is reachable.

### Choosing source chains & currencies

The chains and currencies inside the crypto/exchange pickers aren't hardcoded — they're fetched live from the rail. By default the modal shows a curated subset; narrow it further with `sourceChains` (a CAIP-2 allowlist, also the order) and `sourceCurrencies` (a symbol allowlist where the `'native'` sentinel matches each chain's native asset — ETH, SOL, POL…).

```tsx
<OpenfortProvider
  publishableKey="pk_…"
  uiConfig={{
    funding: {
      sourceChains: ['eip155:8453', 'eip155:42161', 'eip155:137'],
      sourceCurrencies: ['native', 'USDC', 'USDT'],
    },
  }}
>
```

Defaults when omitted: chains `Arbitrum, Base, BNB, Ethereum, Optimism, Polygon, Solana`; currencies `['native', 'USDC', 'USDT']`. A selection is shown only if the rail can route it — a chain or currency the rail doesn't support (e.g. a non-bridgeable native asset) is silently skipped, so you never offer a route that would fail.

## Configuration reference

Everything the Deposit hub reads from `uiConfig`. `fundingBaseUrl` sits at the top level; the rest live under `funding`.

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `fundingBaseUrl` | `string` | — | Base URL of the funding backend. Required for the crypto/exchange rails — omit it and those rows are hidden. |
| `funding.targetChain` | `string` (CAIP-2) | `eip155:8453` (Base) | Chain deposits settle on. |
| `funding.targetCurrency` | `string` (address) | USDC on Base | Token deposits settle as (zero address for native). |
| `funding.targetAddress` | `string` | active embedded wallet | Where funds land. For EVM routes it also receives cross-chain refunds. |
| `funding.methods` | `FundingMethod[]` | all (Apple Pay first on mobile) | Which methods show, and in what order. |
| `funding.sourceChains` | `string[]` (CAIP-2) | curated set above | Allowlist (and order) of source chains in the pickers. |
| `funding.sourceCurrencies` | `string[]` | `['native', 'USDC', 'USDT']` | Allowlist of source currencies; `'native'` matches each chain's native asset. |
| `funding.depositPageUrl` | `string` | `https://deposit.openfort.io` | URL of the [WebView](/docs/configuration/funding/webview) the mobile "Transfer from wallet" deeplinks open. |

## Custom UI with `useFunding`

For a bespoke flow, drive a session yourself with the `useFunding` hook. It exposes the session state plus `fund` (create a session and set a payment method in one call), `createSession` (create a session for a destination, to set the payment method later), `payLink`, and `reset`.

```tsx
import { useFunding } from '@openfort/react'

function Deposit({ walletAddress }: { walletAddress: string }) {
  const { fund, createSession, session, status, loading, error, isAvailable, payLink, reset } = useFunding()

  async function depositFromPolygon() {
    await fund(
      // target — where funds land (USDC on Base)
      { chain: 'eip155:8453', currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', address: walletAddress },
      // source — what the user sends (USDC on Polygon)
      { type: 'evm', source: { chain: 'eip155:137', currency: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', amount: '10000000' } }
    )
  }

  if (session?.paymentMethod) {
    return (
      <div>
        <p>Status: {status}</p>
        <p>Send to: {session.paymentMethod.receiverAddress}</p>
        {/* render session.paymentMethod.addressUri as a QR, or session.paymentMethod.deeplinks */}
      </div>
    )
  }

  return (
    <button type="button" onClick={depositFromPolygon} disabled={loading}>
      {loading ? 'Preparing…' : 'Deposit'}
    </button>
  )
}
```

The hook returns once a deposit address is available and keeps polling until the session reaches a terminal status (`succeeded`, `bounced`, or `expired`). `fund`'s payment method can source from an EVM wallet (`{ type: 'evm', source }`), a Solana wallet (`{ type: 'solana', source }`), or a centralized exchange (`{ type: 'cex', cex, source }`).

### What `useFunding` returns

| Field | Type | Description |
| --- | --- | --- |
| `session` | `FundingSession \| null` | The current or last session, incl. `paymentMethod` once set. |
| `status` | `SessionStatus \| 'idle'` | Lifecycle status; `'idle'` before the first call. |
| `loading` | `boolean` | True while creating the session and minting the address. |
| `error` | `Error \| null` | The last error, if any. |
| `isAvailable` | `boolean` | True when a funding backend is configured (`fundingBaseUrl` set). |
| `fund(target, paymentMethod)` | `=> Promise<FundingSession>` | Create a session and set a payment method in one call. |
| `createSession(target)` | `=> Promise<FundingSession>` | Create a session for a destination, to set the payment method later. |
| `payLink(params)` | `=> Promise<string>` | Resolve a prefilled exchange on-ramp URL (Coinbase / Binance). |
| `reset()` | `=> void` | Clear session state and start over. |

:::tip\[Reacting to settlement]
There's no completion callback — drive UI off `status`. It reaches `succeeded` when funds land (or `bounced` / `expired`). For server-side fulfilment, don't rely on the browser: use [webhooks](/docs/configuration/funding/webhooks).
:::

:::tip
Same‑chain deposits (source chain == destination chain) skip bridging entirely — the hook returns the wallet address directly, so a plain transfer works with no fees.
:::

## Next steps

* Customize or self-host the mobile deposit page — see the [WebView](/docs/configuration/funding/webview).
* Drive funding from a backend or agent with the [Headless API](/docs/configuration/funding/headless).
* React to settlement with [Webhooks](/docs/configuration/funding/webhooks).
