> **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 with hooks

For concepts (sessions, payment methods, statuses) see [Funding](https://www.openfort.io/docs/configuration/funding). For the drop-in Deposit flow in the wallet modal, see [UI configuration](https://www.openfort.io/docs/products/embedded-wallet/react/ui/configuration#funding). This page covers the hooks for a bespoke UI: `useFunding` for the crypto rails, and `useFundingMethods` + `useOnramp` for a custom fiat checkout.

## 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), `track` (attach to a session created elsewhere), `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() {
    const result = 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 ('error' in result) console.error(result.error.shortMessage)
  }

  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 }`) or a Solana wallet (`{ type: 'solana', source }`). To fund from a centralized exchange, use `payLink` instead.

### 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` | `OpenfortError \| null` | The last error, if any. |
| `isAvailable` | `boolean` | True when the funding service is reachable. Defaults to the Openfort backend — set `fundingBaseUrl` only to point at a custom funding service. |
| `fund(target, paymentMethod)` | `=> Promise<{ session } \| { error }>` | Create a session and set a payment method in one call. |
| `createSession(target)` | `=> Promise<{ session } \| { error }>` | Create a session for a destination, to set the payment method later. |
| `track(session)` | `=> Promise<{ session } \| { error }>` | Attach to a session created elsewhere (its `id` + `clientSecret`) and poll it to a terminal status. |
| `payLink(params)` | `=> Promise<{ url } \| { error }>` | Resolve a prefilled Coinbase on-ramp URL. |
| `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](https://www.openfort.io/docs/configuration/webhooks#funding-events).
:::

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

## Resolve fiat methods with `useFundingMethods`

Fiat methods (Apple Pay, Google Pay, card, bank transfer) are resolved **server-side per buyer** — by region and destination. `useFundingMethods` takes a session reference (`{ id, clientSecret }`) and returns the rows to render, in display order:

```tsx
const { methods, country, loaded, loading, error, refresh } = useFundingMethods(session, {
  country: 'US', // optional override; defaults to uiConfig.funding.country, then the request IP
})
```

Each row is a `ResolvedFundingMethod`:

| Field | Meaning |
| --- | --- |
| `method` | `'apple_pay' \| 'google_pay' \| 'card' \| 'bank_transfer'`. |
| `angle` | How the checkout presents — see the [angle table](https://www.openfort.io/docs/configuration/funding#fiat-onramp). |
| `label` | Server-resolved display text; bank transfer shows the regional rail (ACH, SEPA, Interac). |
| `rail?` | `'ach' \| 'sepa' \| 'interac'`, on bank transfers. |
| `requiresDeviceCheck?` | Gate the row on device capability (e.g. Apple Pay needs an Apple device). |
| `providerPublishableKey?` | Initializes the provider elements on `embedded` rows. |

:::warning[Render only what resolves]
Never fall back to a static fiat list. A method absent from `methods` can't commit in the buyer's region — an empty list (after `loaded`) means no fiat is available. Methods also come back empty until you [enable the onramp](https://www.openfort.io/docs/configuration/funding#enabling-it) in the dashboard, and for testnet destinations (fiat is mainnet-only).
:::

## Fiat checkout with `useOnramp`

`useOnramp` takes the session and a resolved method (or a plain method id) and drives the purchase: quote it, commit it, and present the provider checkout.

```tsx
import { useFunding, useFundingMethods, useOnramp } from '@openfort/react'
import { useState } from 'react'

function BuyWithCard({ walletAddress }: { walletAddress: string }) {
  const { createSession } = useFunding()
  const [session, setSession] = useState<{ id: string; clientSecret: string } | null>(null)

  const { methods, loaded } = useFundingMethods(session)
  const card = methods.find((m) => m.method === 'card') ?? null
  const onramp = useOnramp(session, card, { mode: 'redirect' })

  async function start() {
    const result = await createSession({
      chain: 'eip155:8453',
      currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
      address: walletAddress,
    })
    if ('session' in result) setSession(result.session)
  }

  async function buy() {
    // Resolves when the session reaches succeeded | bounced | expired.
    const settled = await onramp.open({ sourceAmount: '25', sourceCurrency: 'USD' })
    console.log(settled.status)
  }

  if (!session) return <button type="button" onClick={start}>Add funds</button>
  if (!loaded) return <p>Loading payment methods…</p>
  if (!card) return <p>Card isn't available in your region.</p>
  return (
    <button type="button" onClick={buy} disabled={onramp.loading}>
      Buy $25 with card
    </button>
  )
}
```

### What `useOnramp` returns

| Field | Type | Description |
| --- | --- | --- |
| `open(params?)` | `=> Promise<FundingSession>` | Commit the method and present the checkout. Resolves at a **terminal** status (`succeeded` | `bounced` | `expired`). Params: `sourceAmount`, `sourceCurrency`, `redirectUrl`, and the wallet-pay identity for native Apple / Google Pay. |
| `quote(params)` | `=> Promise<OnrampQuote>` | Price the purchase — `{ sourceAmount, sourceCurrency }` in, fees + delivered amount + rate out. |
| `status` | session status | `'idle'` | Lifecycle status of the committed session. |
| `url` | `string \| null` | The checkout URL once committed. |
| `angle` | `'popup' \| 'native' \| 'embedded' \| null` | How the checkout presents — see the [angle table](https://www.openfort.io/docs/configuration/funding#fiat-onramp). |
| `loading` | `boolean` | True from `open()` until the checkout is presented (not until settlement). |
| `checkoutClosed` | `boolean` | The buyer closed the hosted window while the session was still pending. **Not an outcome** — they may have paid right before closing; polling continues. |
| `present()` | `=> void` | Re-open the same committed checkout in a fresh popup (e.g. after `checkoutClosed`). |
| `session`, `error`, `reset()` | | The committed session, last error, and state reset. |

Settlement is provider-webhook-driven server-side — the session status is the source of truth, never the popup window. A session accepts **one** payment method; to retry after `bounced` or `expired`, create a fresh session.

### Presenting the checkout

`open()` presents `popup`-angle checkouts according to `options.mode`:

* `'popup'` (default) — `window.open` in a new window. **From your own button this is usually popup-blocked**: the commit round-trip outlives the browser's user-activation window. Prefer one of the modes below, or synchronously `window.open('about:blank', '_blank')` inside the click handler and hand the checkout URL to that window yourself (`mode: 'manual'`).
* `'redirect'` — navigate the current page to the checkout; pass `redirectUrl` so the provider returns the buyer to you.
* `'manual'` — present nothing; read `url` / `angle` and render it yourself. Popup and native URLs must **never** be iframed — providers block framing.

`native`-angle URLs (US Apple / Google Pay) behave differently:

* They are **never auto-presented** — mount them in-page yourself.
* They require an OTP-verified buyer identity, passed as `open({ walletPay })`. Openfort issues those OTPs via [`openfort.funding.verifications`](https://www.openfort.io/docs/products/embedded-wallet/javascript/signer/funding#wallet-pay-apple-pay--google-pay) in `@openfort/openfort-js`.
* The wallet modal does all of this for you.

## Next steps

* Drive funding from a backend or agent with the [JavaScript SDK](https://www.openfort.io/docs/products/embedded-wallet/javascript/signer/funding) or the [REST reference](https://www.openfort.io/docs/configuration/funding/headless).
* Render the mobile deposit page with the [hosted deposit page](https://www.openfort.io/docs/configuration/funding#hosted-deposit-page).
* React to settlement with [Webhooks](https://www.openfort.io/docs/configuration/webhooks#funding-events).
