> ## Documentation Index
> Fetch the complete documentation index at: https://bkey.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rails

> Every channel money can take into or out of a wallet — which direction it runs, what it converts between, and what identifier the destination needs.

<div className="bmoni-spine">
  <span>Lifecycle</span>
  <a data-stage="1" href="/lifecycle#1-create-the-user">User</a>
  <a data-stage="2" href="/lifecycle#2-provision-the-smart-wallet">Wallet</a>
  <a data-stage="3" href="/lifecycle#3-verify-identity-kyc">KYC</a>
  <a data-stage="4" href="/lifecycle#4-activate-the-rail">Rail</a>
  <a data-stage="5" href="/lifecycle#5-fund-the-wallet">Fund</a>
  <a data-stage="6" href="/lifecycle#6-move-money">Move money</a>
</div>

A wallet holds tokens. A **rail** is a channel that moves value between those tokens and the outside world — a bank transfer, a cash agent, an on-chain address. Picking the rail is usually the first decision: it determines the currency, the direction, and the identifier you have to collect from the user.

***

## Every rail

| Rail                     | Direction | Converts                     | Destination needs                                              | Reference                               |
| ------------------------ | --------- | ---------------------------- | -------------------------------------------------------------- | --------------------------------------- |
| USD virtual bank account | in        | USD → `USDB`                 | — (the user receives account + routing numbers)                | [USD VBA](/api-reference/usd-vba)       |
| NGN virtual account      | in        | NGN → `CNGN`                 | — (the user receives a NUBAN)                                  | [NGN rails](/api-reference/ngn-rails)   |
| NGN bank withdrawal      | out       | `CNGN` / `USDB` → NGN        | NUBAN + CBN bank code                                          | [NGN rails](/api-reference/ngn-rails)   |
| Bank payout              | out       | `USDB` → local currency      | country + bank ID, account number (+ branch / routing / SWIFT) | [below](#bank-payouts)                  |
| SEPA payout              | out       | `EURe` → EUR                 | IBAN                                                           | [EU SEPA](/api-reference/eu-sepa)       |
| MXN ramp                 | in & out  | MXN ↔ `MEXe`                 | CLABE                                                          | [MXN ramp](/api-reference/mxn-ramp)     |
| Cash order               | in & out  | local cash ↔ wallet currency | a cash order, redeemed in person                               | [LATAM cash](/api-reference/latam-cash) |
| Crypto transfer          | in & out  | — (token stays a token)      | on-chain address + chain                                       | [below](#crypto-in-and-out)             |

Swapping between two tokens you already hold is not a rail — no money enters or leaves. That is `POST /v1/users/{userId}/exchange/convert`.

<Note>
  A rail becoming available is gated on the matching onboarding, not just on KYC being complete. Each currency needs its own activation — see [the integration flow](/api-reference/integration-flow) and the per-region [KYC pages](/api-reference/kyc-usd-requirements).
</Note>

***

## The signature pattern

Most outbound rails do not move money on the call that creates them. They return something to sign, and the transfer only proceeds once the signature comes back:

1. Create the payout / order. The response carries a `signatureRequest` (or a `workflowId` plus a hash), usually alongside a quote.
2. Sign it on-device with the wallet owner key via `bmoni_embedded_sdk` — the key registered as `userOwnerAddress` at wallet creation.
3. Submit it:
   ```http theme={null}
   POST /v1/users/{userId}/wallets/submit-signature
   { "workflowId": "…", "signature": "0x…" }
   ```

The Nigerian offramp is the exception in shape: it creates a *proposal* that needs approval before signing. See [NGN rails](/api-reference/ngn-rails#5-sign-the-proposal).

***

## Bank payouts

A single rail that pays out to bank accounts across many countries, funded from `USDB`. Use it when the destination country has no dedicated rail of its own.

### 1. Find the country and bank

```http theme={null}
GET /v1/users/{userId}/payouts/countries
GET /v1/users/{userId}/payouts/banks?country=NGA
GET /v1/users/{userId}/payouts/bank-branches?bankId=bank_123
```

`banks` takes an ISO alpha-3 country code and returns bank IDs to use downstream. Branches only exist for some banks — treat an empty list as "no branch needed", not an error.

### 2. Validate the destination

```http theme={null}
POST /v1/users/{userId}/payouts/validate-account
{
  "country": "NGA",
  "currency": "NGN",
  "bankId": "bank_123",
  "accountNumber": "0123456789"
}
```

`routingNumber` is also accepted, for rails that require one. Validate before creating the payout so the user can fix a wrong account number while it is still cheap.

### 3. Create the payout

```http theme={null}
POST /v1/users/{userId}/payouts
{
  "sourceSmartWalletId": "7f4d6b88-80a0-4d3f-9538-9a4dfabc1234",
  "amount": "100000000",
  "country": "NGA",
  "currency": "NGN",
  "bankDetails": {
    "bankId": "bank_123",
    "accountNumber": "0123456789",
    "accountHolderName": "Jane Doe"
  },
  "note": "Invoice 12345"
}
```

| Field                           |                                                                     |
| ------------------------------- | ------------------------------------------------------------------- |
| `sourceSmartWalletId`           | required — the wallet funding the payout, in `USDB`                 |
| `amount`                        | required — **`USDB` minor units** as a string, not a decimal amount |
| `country`                       | required — ISO alpha-3                                              |
| `currency`                      | required — ISO 4217 payout currency                                 |
| `bankDetails.bankId`            | required — from the `banks` lookup                                  |
| `bankDetails.accountNumber`     | required                                                            |
| `bankDetails.accountHolderName` | required                                                            |
| `bankDetails.branchId`          | optional — from the `bank-branches` lookup                          |
| `bankDetails.accountType`       | optional — e.g. `savings`, `checking`                               |
| `bankDetails.routingNumber`     | optional                                                            |
| `bankDetails.swiftCode`         | optional                                                            |
| `note`                          | optional — max 500 characters                                       |

<Warning>
  `amount` here is in **minor units** — `"100000000"`, not `"100.00"`. This is inconsistent with the Nigerian offramp, where `fromAmount` is a decimal string like `"100.00"`. Sending `"100.00"` to a minor-units field, or a minor-units integer to a decimal one, will not be rejected as a type error; it will move the wrong amount. Check which one you are calling.
</Warning>

The response is a signature request plus a quote. Finish it with the [signature pattern](#the-signature-pattern) above.

***

## Crypto in and out

The wallet's tokens can also arrive from, or leave to, an external on-chain address. No fiat is involved, so there is no quote and no local identifier.

### In — get a deposit address

```http theme={null}
GET  /v1/deposit/supported-assets
POST /v1/users/{userId}/deposit/wallet
```

`supported-assets` lists the enabled chains and tokens; it is not user-scoped, so you can call it before onboarding to build a picker. `deposit/wallet` returns the address to show:

```json theme={null}
{ "address": "0x1234567890abcdef1234567890abcdef12345678", "chain": "Base", "currency": "USDC" }
```

Tokens seen at that address are credited to the wallet. Supported currencies include `USDC`, `USDT`, `USDB`, `DAI`, `EURC`, `PYUSD`, and `USDP` — call `supported-assets` for the live list rather than hardcoding it.

### Out — send to an external address

```http theme={null}
GET  /v1/users/{userId}/withdrawal/smart-wallet/crypto/supported/{currency}
POST /v1/users/{userId}/withdrawal/smart-wallet/crypto
```

```json theme={null}
{
  "sourceSmartWalletId": "7f4d6b88-80a0-4d3f-9538-9a4dfabc1234",
  "destinationChain": "Base",
  "destinationCurrency": "USDC",
  "destinationAddress": "0x1234abcd…"
}
```

Check `supported/{currency}` first — the chain and token pair has to be one the rail actually supports. This returns a signature request; finish it with the [signature pattern](#the-signature-pattern).

***

## Related

* [Supported regions](/api-reference/supported-regions) — which rail each region gets, and the KYC it requires
* [Integration flow](/api-reference/integration-flow#wallet-home-operations) — every wallet-home endpoint in one table
