> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# Funding

Funding lets a user add crypto to their Openfort wallet from **any chain, any token, or a centralized exchange** — and have it arrive as the token you want on the chain you want. Under the hood it mints a single deposit address per route and bridges/swaps the inbound funds to the destination, so your users never have to think about networks or wrapping.

It is the same primitive whether a human taps a button or an agent calls an endpoint: one **funding session** with a destination and a source.

<img src="/img/funding-docs.png" alt="Openfort Funding — deposit crypto from a wallet, exchange, card, or Apple Pay" className="w-full rounded-xl border border-primary" />

## Ways to integrate

Pick the surface that fits your app. They're all clients over the same session API, so you can mix them.

* [React modal](https://www.openfort.io/docs/products/embedded-wallet/react/ui/configuration#funding) — Drop-in Deposit flow in the @openfort/react wallet modal.
* [React hook](https://www.openfort.io/docs/products/embedded-wallet/react/wallet/funding) — The useFunding hook for a custom React Deposit UI.
* [React Native](https://www.openfort.io/docs/products/embedded-wallet/react-native/wallet/funding) — Open the hosted deposit page in a React Native WebView.
* [Headless / API](https://www.openfort.io/docs/configuration/funding/headless) — The REST and session-model reference behind every funding integration.

## How a session works

A funding session is one deposit attempt against a destination: create it, attach a source, then watch it settle.

:::steps
### Create a session

Specify where the funds should land — a `target` of chain (CAIP‑2), token, and wallet address. You get back a session `id` and a `clientSecret`.

```ts
type FundingTarget = { chain: string; currency: string; address: string } // CAIP-2 destination
```

### Set a payment method

Commit the source the user will send from (an EVM or Solana wallet, or an exchange). This mints a **deposit address** and returns a QR + wallet deeplinks.

```ts
type FundingSource = { chain: string; currency: string; amount: string } // where the user sends from
```

### Poll until settled

Read the session back with `get`, or subscribe to [webhooks](https://www.openfort.io/docs/configuration/webhooks#funding-events), until it reaches one of the terminal states below.
:::

### Session lifecycle

A session advances along a single path and stops at one of three terminal states.

requires\_payment\_method
→
waiting\_payment
→
processing
→
succeeded ✓

Instead of `succeeded`, a session can end at `bounced` (delivery failed, funds refunded) or `expired` (no deposit arrived). Both are terminal.

| Status | What it means | |
| --- | --- | --- |
| `requires_payment_method` | Session created; no source committed yet. | |
| `waiting_payment` | Deposit address minted; waiting for the inbound transfer. | |
| `processing` | Deposit detected; bridging/swapping to the destination. | |
| `succeeded` | Funds delivered to the destination wallet. | Terminal |
| `bounced` | Delivery failed; funds refunded on the source chain. | Terminal |
| `expired` | No deposit arrived before the session expired. | Terminal |

## Payment methods

A user can fund a wallet four ways. The first two are **transfers** that run through the session lifecycle above; the last two **buy** crypto on an external rail and settle straight to the wallet.

| Method | How the user pays | Rail |
| --- | --- | --- |
| **Self-custody wallet** | Sends from MetaMask, Phantom, Coinbase Wallet… — deeplink or QR | Funding session — `paymentMethod` `evm` / `solana` |
| **Exchange withdrawal** | Withdraws from Binance / Coinbase to the deposit address (guided network, min, memo) | Funding session — `paymentMethod` `cex` |
| **Exchange on-ramp** | Buys on an exchange on-ramp (Coinbase; Binance soon); delivered to the wallet | `pay_link` helper (not a session) |
| **Card / Apple Pay** | Fiat purchase | Existing [Buy](https://www.openfort.io/docs/products/embedded-wallet/react/ui) on-ramp |

:::note
Only the two **transfer** methods emit session status — they mint a deposit address and bridge to the destination. The on-ramp and fiat rails settle directly to the wallet and don't run through the session lifecycle. **Card / Apple Pay (fiat) is available in the React modal only — not yet via the headless API or SDK.**
:::

## Credentials

Every funding call uses two credentials:

| Credential | Where it lives | Scope |
| --- | --- | --- |
| **Publishable key** (`pk_…`) | `Authorization: Bearer` header, client or server | Identifies your project. Safe to ship to the browser. |
| **`clientSecret`** | Returned by `create`; held by the client driving the session | Scopes reads and writes to that one session. Treat it like a one-time token — never log it or reuse it across sessions. |

:::note
A secret-key **server flow** — create a session for any user, list sessions across your project — is planned. Until then, both browser and server integrations authenticate with the publishable key plus the session `clientSecret`.
:::

## Hosted deposit page

The modal's **Transfer from wallet** method sends users to a standalone **deposit send page**. On mobile the modal builds a one-tap deeplink that opens this page inside the user's wallet app, where the in-app browser injects a wallet provider — `window.ethereum` for EVM wallets, or the Solana provider (`window.solana` / the Solana Wallet Standard) when the embedded wallet is on Solana. The page reads the transfer from its URL and submits it through that provider. No backend, no session, and no Openfort SDK run on the page.

:::info
On **desktop**, "Transfer from wallet" sends it directly through the user's connected browser-extension wallet (MetaMask, Rabby, …).
:::

The page reads the transfer entirely from the query string:

| param | required | meaning |
| --- | --- | --- |
| `to` | yes | deposit address (the funds receiver) |
| `chainId` | yes | numeric source chain id (e.g. `42161`) |
| `token` | no | ERC-20 contract or SPL-token mint; omit for the chain's native token |
| `decimals` | no | token decimals (default `18`) |
| `symbol` | no | display symbol |
| `chain` | no | display chain name |
| `amount` | no | preset amount in base units; user-editable |

```
https://deposit.openfort.io?to=0xReceiver…&chainId=42161&token=0xaf88…5831&decimals=6&symbol=USDC&chain=Arbitrum&amount=10000000
```

The page shows the transfer for review, connects the injected wallet and switches to the source chain (EVM only — Solana has no chain switch), then submits the transfer (`eth_sendTransaction` for EVM, `signAndSendTransaction` for Solana). On success it shows the transaction hash. It does **not** call back into a host app — track settlement with [Webhooks](https://www.openfort.io/docs/configuration/webhooks#funding-events) or by polling the session through the [Headless API](https://www.openfort.io/docs/configuration/funding/headless).

### Self-hosting

The page is a single static `index.html` — no build step, no third-party scripts. Host it at a domain root and point the modal at your copy with `uiConfig.funding.depositPageUrl` (default `https://deposit.openfort.io`):

```tsx
<OpenfortProvider
  publishableKey="pk_…"
  uiConfig={{ funding: { depositPageUrl: 'https://deposit.yourapp.com' } }}
>
```

:::warning
This page triggers token transfers from the user's wallet, so the origin must be trusted. Serve it over HTTPS from a domain you control, keep it self-contained (no trackers or analytics), and review changes like any code that moves funds. The user still confirms the destination and amount in their wallet.
:::

:::tip
Building an agent or automated treasury? Funding is agent‑native: give it a destination and it returns a deposit address that settles token X on chain Y into the wallet — no human in the loop. See the [REST reference](https://www.openfort.io/docs/configuration/funding/headless).
:::
