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

For concepts (sessions, statuses, refunds) and the underlying HTTP API see [Funding](https://www.openfort.io/docs/configuration/funding) and the [REST reference](https://www.openfort.io/docs/configuration/funding/headless). This page covers the `openfort.funding` namespace in `@openfort/openfort-js` — the crypto rails and, from **2.2.0**, the full [fiat onramp](#fiat-onramp) client (sessionless discovery and the `angles` capability filter arrived in **2.3.0**).

## Using the SDK

`@openfort/openfort-js` exposes funding under `openfort.funding` — usable from any JavaScript runtime (browser or server) with your publishable key.

When the source route is known upfront, funding is **one call plus a wait** — pass `paymentMethod` at creation and the session comes back with the deposit address. The SDK remembers each session's `clientSecret`, so follow-up calls don't need to thread it.

```ts
import { Openfort } from '@openfort/openfort-js'

const openfort = new Openfort({
  baseConfiguration: { publishableKey: process.env.OPENFORT_PUBLISHABLE_KEY! },
})

// Required before accessing openfort.funding (like auth / embeddedWallet / user)
// — see https://www.openfort.io/docs/products/embedded-wallet/javascript/use-openfort
await openfort.waitForInitialization()

// One call: create the session AND mint the deposit address.
// Destination: USDC on Base. Source: USDC on Polygon.
const session = await openfort.funding.sessions.create({
  target: {
    chain: 'eip155:8453',
    currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    address: '0xUserWalletAddress',
  },
  paymentMethod: {
    type: 'evm',
    source: { chain: 'eip155:137', currency: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', amount: '10000000' },
  },
})

console.log(session.status)                          // "waiting_payment"
console.log(session.paymentMethod?.receiverAddress)  // address the user sends to
console.log(session.paymentMethod?.addressUri)       // EIP-681 / Solana Pay URI for a QR

// Wait for settlement (polls until succeeded | bounced | expired).
const settled = await openfort.funding.sessions.wait(session.id, { pollMs: 4000, timeoutMs: 600000 })
console.log(settled.status) // "succeeded"
```

Good to know:

* **`wait` options** — `{ clientSecret?, pollMs?, timeoutMs? }`. `pollMs` defaults to `4000`; `timeoutMs` to `1800000` (30 min); `clientSecret` is filled from the remembered session when omitted.
* **Two-step flow** — when the user picks the source later, call `sessions.create({ target })` then `sessions.setPaymentMethod(session.id, { paymentMethod })`. The SDK fills in the remembered `clientSecret`; pass it explicitly for sessions created elsewhere.
* **`create` options** — `amountUnits` (lock the deposit to a fixed amount, destination base units), `metadata`, `externalId` (idempotency key — reusing it returns the existing session), `strict` (`true` mints a single-use deposit address).
* **Lifecycle** — `requires_payment_method` → `waiting_payment` → `processing` → `succeeded`, or terminal `bounced` / `expired`. Full field list in the [REST reference](https://www.openfort.io/docs/configuration/funding/headless).

:::note
A `@openfort/openfort-node` server namespace is planned; until then, server-side integrations call the [REST endpoints](https://www.openfort.io/docs/configuration/funding/headless#rest-api) directly.
:::

## Payment method types

`setPaymentMethod` (or the inline `paymentMethod` on `create`) takes a crypto source or a fiat onramp method:

:::code-group
```ts [EVM]
{ type: 'evm', 
  source: { 
  chain: 'eip155:137', 
  currency: '0x3c49…3359', 
  amount: '10000000' } 
  }
```

```ts [Solana]
{ type: 'solana', 
  source: { 
    chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 
    currency: 'EPjF…Dt1v', 
    amount: '10000000' } 
  }
```

```ts [Onramp]
{ type: 'onramp',
  method: 'card',        // 'apple_pay' | 'google_pay' | 'card' | 'bank_transfer'
  sourceAmount: '25',    // fiat, human units
  sourceCurrency: 'USD',
  country: 'US',         // optional; defaults to the request IP
}
```
:::

`evm` / `solana` are self-custody transfers — the response carries `receiverAddress`, `addressUri`, and wallet `deeplinks`. `onramp` is a [fiat purchase](#fiat-onramp) — the response carries the checkout `url` and `angle`. For every response field, see the [REST reference](https://www.openfort.io/docs/configuration/funding/headless#payment-method-types). To fund from a centralized exchange instead, use `payLink`.

:::tip[Show fees and the minimum before the user sends]
Once a payment method is set, `session.paymentMethod.fees` (an array of `{ kind, amount, currency }` where `kind` is `gas` | `relayerGas` | `relayerService` | `app`) and `session.paymentMethod.minAmount` (in base units, or `null` for no floor) carry the cost and minimum for the route. Surface both so the user sees what it costs and the smallest amount they can send before they transfer.
:::

:::warning[Set `refundTo` for cross-VM routes]
A `bounced` session refunds on the **source** chain. On a cross-VM route (e.g. Solana → Base) the destination address isn't valid there, so set `refundTo` on the payment method to an address you control on the source chain — otherwise the refund can't be delivered. See [Refunds](https://www.openfort.io/docs/configuration/funding/headless#refunds).
:::

## Fiat onramp

The onramp is the same session with a `type: 'onramp'` payment method — no customer backend, publishable key only.

:::steps
### Create the session

```ts
const session = await openfort.funding.sessions.create({
  target: {
    chain: 'eip155:8453',
    currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    address: '0xUserWalletAddress',
  },
})
```

### Resolve the buyer's methods

Which fiat methods can **this** buyer use? Region + destination, server-resolved:

```ts
const { methods } = await openfort.funding.sessions.methods(session.id, { country: 'US' })
// → [{ method: 'card', angle: 'popup', label: 'Card', … }, …]
```

Render **only** these — an absent method can't commit in the buyer's region, and the list is empty until you [enable the onramp](https://www.openfort.io/docs/configuration/funding#enabling-it) in the dashboard.

### Quote it (optional)

```ts
const quote = await openfort.funding.sessions.quote(session.id, {
  method: 'card', sourceAmount: '25', sourceCurrency: 'USD',
})
```

### Commit and present the checkout

The session comes back `waiting_payment` with the checkout URL:

```ts
const committed = await openfort.funding.sessions.setPaymentMethod(session.id, {
  paymentMethod: { type: 'onramp', method: 'card', sourceAmount: '25', sourceCurrency: 'USD' },
})
if (committed.paymentMethod?.type === 'onramp') {
  window.open(committed.paymentMethod.url!, '_blank') // popup angle: new tab/popup, never an iframe
}
```

Present it per the payment method's `angle` (`popup` | `native` | `embedded`) — see the [angle table](https://www.openfort.io/docs/configuration/funding#fiat-onramp).

### Wait for settlement

Settlement is provider-webhook-driven — the session status is the source of truth, never the checkout window:

```ts
const settled = await openfort.funding.sessions.wait(session.id)
console.log(settled.status) // "succeeded"
```
:::

A session accepts **one** payment method — create a fresh session to retry.

### Sessionless discovery and client capability (2.3.0)

To render funding options before any session exists, resolve the same rows with `funding.methods()`:

```ts
const { methods } = await openfort.funding.methods({
  targetChain: 'eip155:8453',
  targetCurrency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  country: 'US',       // optional; defaults to the request IP
  angles: ['popup'],   // optional; declare what this client can execute
})
```

`angles` is the client's capability declaration — the [presentation angles](https://www.openfort.io/docs/configuration/funding#fiat-onramp) it can actually run:

* **Routing degrades instead of failing**: providers whose flow resolves to an excluded angle are skipped, falling back to the hosted popup. A React Native client sends `['popup']` so a US card row never resolves to `embedded` (which it can't execute).
* **Be consistent**: pass the same `angles` on `sessions.methods`, `sessions.quote`, and the `onramp` payment method so resolution, quote, and commit agree.
* **Omit it** for no restriction.

### Wallet pay (Apple Pay / Google Pay)

The native Apple / Google Pay sheet requires an OTP-verified buyer identity on the commit. Openfort issues the codes via `openfort.funding.verifications`:

```ts
// One verification per channel: email and sms (US mobile, E.164)
const start = await openfort.funding.verifications.create({ channel: 'sms', destination: '+14155550123' })
const record = await openfort.funding.verifications.submit(start.verificationId, '123456')
```

* **Verify both channels**, then pass the identity on the payment method: `email`, `phoneNumber`, `phoneNumberVerifiedAt`, `agreementAcceptedAt` (the buyer's guest-checkout consent), `smsVerificationId`, `emailVerificationId`.
* Verifications stay valid **~60 days**; on test keys, sandbox numbers (`+1000` + 7 digits) are accepted.
* Wallet pay runs on **guest checkout** with purchase limits — `openfort.funding.walletPay.limits({ phoneNumber, method })` returns the remaining allowance **in minor units (cents)**, and `startLimitUpgrade` mints a provider-hosted upgrade URL when the buyer needs a higher ceiling.

:::note
In React and React Native, don't rebuild this — [`useOnramp`](https://www.openfort.io/docs/products/embedded-wallet/react/wallet/funding#fiat-checkout-with-useonramp) and [`useWalletPayVerification` + `OnrampPaymentSheet`](https://www.openfort.io/docs/products/embedded-wallet/react-native/wallet/funding#fiat-onramp-apple-pay--google-pay) wrap these calls.
:::

### Embedded checkout (in-page card element)

A method resolved with `angle: 'embedded'` keeps the card checkout inside your page. The buyer authenticates and enters card details in the **provider's** elements — mounted client-side with the `providerPublishableKey` on that method row — and `openfort.funding.embedded` carries the handshake around them. Card data never reaches your code or ours.

:::warning[Use the SDKs for this one]
This is the only funding flow with a client-side provider dependency. The React [wallet modal](https://www.openfort.io/docs/products/embedded-wallet/react/ui/configuration#funding) and [`useOnramp`](https://www.openfort.io/docs/products/embedded-wallet/react/wallet/funding#fiat-checkout-with-useonramp) drive it end to end — reach for the calls below only when you're building the element handshake yourself on a platform we don't ship a hook for.
:::

:::steps
### Mint the auth intent

Mount the provider's auth element with the returned id; it yields the authenticated buyer's `customerRef`:

```ts
const intent = await openfort.funding.embedded.createAuthIntent({ email: 'buyer@example.com' })
const { customerRef } = await mountProviderAuthElement(intent.id) // your provider element
```

### Exchange the intent for its token

The token stays server-side — the commit and checkout look it up by intent id:

```ts
await openfort.funding.embedded.exchangeToken(intent.id)
```

### Read identity and limits (optional)

What has this buyer already satisfied, and what may they spend?

```ts
const identity = await openfort.funding.embedded.identity({ authIntentId: intent.id, customerRef })
// → { region: 'eu' | 'us' | null, level: 'L0' | … | 'REQUIRES_KYC', providedFields: string[] }

const { remainingMinor } = await openfort.funding.embedded.limits({ authIntentId: intent.id })
// → remaining spend in MINOR units (cents); null means the provider gave no answer
```

### Collect the card and commit

Collect the card in the provider's payment element, then commit with the `embedded` block:

```ts
const { paymentToken } = await mountProviderPaymentElement() // your provider element
const committed = await openfort.funding.sessions.setPaymentMethod(session.id, {
  paymentMethod: {
    type: 'onramp', method: 'card', sourceAmount: '25', sourceCurrency: 'USD',
    embedded: { authIntentId: intent.id, customerRef, paymentToken },
  },
})
```

This creates a *headless* provider session: `url` is `null`, and `providerSessionId` identifies the checkout.

### Redeem the checkout secret

Hand the one-shot element secret to the provider's checkout call:

```ts
if (committed.paymentMethod?.type === 'onramp') {
  const { clientSecret } = await openfort.funding.sessions.checkout(session.id)
  await performProviderCheckout(committed.paymentMethod.providerSessionId!, clientSecret)
}
```

### Wait for settlement

The session — not the element's result — is the settlement source of truth:

```ts
const settled = await openfort.funding.sessions.wait(session.id)
```
:::

Behavior worth knowing:

* `createAuthIntent` and `exchangeToken` **throw** until the provider's embedded access is configured for your deployment.
* `identity` and `limits` are **best-effort** — on failure, fall back to asking the buyer for more rather than blocking the purchase. `identity` mainly narrows the extra EU identity sub-steps; a `null` limit means unrestricted, not zero.
* `sessions.checkout` is **one-shot** and only valid after an `embedded` commit; mandate acceptance happens server-side on that call.
* Retrying a purchase means a fresh session.
* The same flow over HTTP is in the [REST reference](https://www.openfort.io/docs/configuration/funding/headless#embedded-checkout).

## `fund()` — create and wait in one call

`openfort.funding.fund` is a shorthand for the create-and-wait flow [shown above](#using-the-sdk) — it bundles `sessions.create({ paymentMethod })` and `sessions.wait` into one call, resolving with the terminal session (`succeeded`, `bounced`, or `expired`) and rejecting on timeout.

```ts
const settled = await openfort.funding.fund({
  target: {
    chain: 'eip155:8453',
    currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    address: '0xUserWalletAddress',
  },
  paymentMethod: {
    type: 'evm',
    source: { chain: 'eip155:137', currency: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', amount: '10000000' },
  },
  wait: { pollMs: 4000, timeoutMs: 600000 }, // optional; omit to use sessions.wait defaults (4000 / 30 min)
})

console.log(settled.status) // "succeeded"
```

It takes the same session options as `create` (`amountUnits`, `metadata`, `externalId`, `strict`) plus an optional `wait` for the poll interval and timeout. `fund` accepts **crypto payment methods only** (`evm` / `solana`) — an onramp purchase needs its checkout presented mid-flow, so use the [step-by-step flow](#fiat-onramp) instead.

## `payLink()` — buy or transfer from an exchange

`openfort.funding.payLink` returns a prefilled Coinbase / exchange **Transfer funds** URL that settles straight to the session's wallet. It's session-bound — the destination comes from the session, so the caller only chooses the `amount` (and optionally `asset`, which defaults to `USDC`); the `clientSecret` is filled from the remembered session.

```ts
const session = await openfort.funding.sessions.create({ target })
const url = await openfort.funding.payLink({ sessionId: session.id, amount: '25' })
// → open `url` so the user can send from their exchange
```

This is a separate rail from a session payment method — use it to let a user **buy** or transfer from a centralized exchange rather than send from a self-custody wallet.

## `chains()` — list source chains

`openfort.funding.chains` returns the routable source chains and currencies live from the rail — build a source picker from it instead of hardcoding. Pass `{ livemode: false }` for the testnet rail.

```ts
const chains = await openfort.funding.chains()
// → FundingChain[] — same shape as the REST /chains response
```

See [Supported chains & currencies](https://www.openfort.io/docs/configuration/funding/headless#supported-chains--currencies) for the field shape and a copy-paste table of common chains.

## Next steps

* Read the full HTTP API, session options, and response fields in the [REST reference](https://www.openfort.io/docs/configuration/funding/headless).
* Build a Deposit UI in React with the [`useFunding` hook](https://www.openfort.io/docs/products/embedded-wallet/react/wallet/funding) or the [wallet modal](https://www.openfort.io/docs/products/embedded-wallet/react/ui/configuration#funding).
* React to settlement with [Webhooks](https://www.openfort.io/docs/configuration/webhooks#funding-events).
