# 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.

## How a session works

A funding session is one deposit attempt against a destination.

:::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`.

### 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.

### Poll until settled

The session moves through `requires_payment_method → waiting_payment → processing → succeeded` (or `bounced` / `expired`). Read it back, or subscribe to [webhooks](/docs/configuration/webhooks#funding-events).
:::

```ts
type FundingTarget = { chain: string; currency: string; address: string } // CAIP-2 destination
type FundingSource = { chain: string; currency: string; amount: string }  // where the user sends from
type FundingStatus =
  | 'requires_payment_method'
  | 'waiting_payment'
  | 'processing'
  | 'succeeded'
  | 'bounced'
  | 'expired'
```

### Session statuses

A session moves forward through these states and stops at one of three terminal ones. Read the current status from `get`, or subscribe to [webhooks](/docs/configuration/webhooks#funding-events).

| Status | What it means | Terminal |
| --- | --- | --- |
| `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. | ✓ |
| `bounced` | Delivery failed; funds refunded on the source chain. | ✓ |
| `expired` | No deposit arrived before the session expired. | ✓ |

## 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](/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`.
:::

## Ways to integrate

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

<HoverCardLayout>
  <HoverCardLink description="Drop-in Deposit flow in the @openfort/react wallet modal." href="/products/embedded-wallet/react/ui/configuration#funding" title="React modal" icon={LayoutTemplateIcon} />

  <HoverCardLink description="The useFunding hook for a custom React Deposit UI." href="/products/embedded-wallet/react/wallet/funding" title="React hook" icon={CodeIcon} />

  <HoverCardLink description="Open the hosted deposit page in a React Native WebView." href="/products/embedded-wallet/react-native/wallet/funding" title="React Native" icon={SmartphoneIcon} />

  <HoverCardLink description="The REST and session-model reference behind every funding integration." href="/configuration/funding/headless" title="Headless / API" icon={TerminalIcon} />
</HoverCardLayout>

## 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](/docs/configuration/webhooks#funding-events) or by polling the session through the [Headless API](/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](/docs/configuration/funding/headless).
:::
