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

# NGN rails (StRails)

> Temporary VA onramps, cNGN↔USDC/USDT wallet swaps, and cNGN withdraws on StRails Smart Wallets — separate from Solana Relay swaps and PayCrest offramps.

RemitFlex **rails** APIs move NGN/cNGN on a customer's [StRails](https://docs.strails.co) Smart Wallet (Base): create a **temporary fixed-amount VA** for onramp (third-party OK), swap between `CNGN`, `USDC`, and `USDT`, withdraw tokens to an external wallet, or offramp cNGN to a Nigerian bank account.

These are **not** the same as:

| Product                                     | What it does                                        |
| ------------------------------------------- | --------------------------------------------------- |
| [Solana swaps](/products/swaps)             | Relay EURC↔USDC deposit-address swaps (`/v1/swaps`) |
| [Local fiat offramps](/products/fiat-rails) | PayCrest stablecoin → NGN (`/v1/offramps`)          |

**Required scopes:** `transfers:read`, `transfers:write`

**Prerequisite:** Link the customer to a StRails user (`POST /v1/customers/:id/strails/link`) and ensure the Smart Wallet is **StRails-controlled**. If a custom EOA `owner` was set on the wallet, StRails cannot execute API withdraws or swaps for that wallet.

## Permanent vs temporary virtual accounts

|             | Permanent user VA           | Temporary VA onramp                                               |
| ----------- | --------------------------- | ----------------------------------------------------------------- |
| Lifetime    | Never expires               | \~30 minutes                                                      |
| Amount      | Any                         | Exact amount (includes fees)                                      |
| Who can pay | User's own bank (BVN match) | Third parties OK                                                  |
| How         | Synced on customer link     | `POST /v1/rails/onramps`                                          |
| Destination | User Smart Wallet (cNGN)    | Default: sweep cNGN to user Smart Wallet (`sweepToOfframp: true`) |

## Temporary VA onramp (NGN → Smart Wallet)

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/onramps \
  -H "Authorization: Bearer $REMITFLEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "customerId": "optional-customer-uuid",
    "sweepToOfframp": true,
    "autoSwap": false
  }'
```

| Field            | Description                                                                                    |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| `amount`         | Requested Naira amount (base). StRails adds fees — pay **`payAmount` / `totalAmount`** exactly |
| `sweepToOfframp` | Default `true` — after mint, StRails should sweep cNGN to the user's permanent Smart Wallet    |
| `autoSwap`       | Optional — swap cNGN → `assetSwap` before sweep                                                |
| `assetSwap`      | `USDC` or `USDT` (required when `autoSwap` is true)                                            |
| `customerId`     | Optional; defaults to org primary customer                                                     |

The linked customer `evmWallet` is the **permanent** Smart Wallet and is sticky: Sync / `getUserDetails` must never promote a temporary onramp wallet into that field.

If StRails auto-sweep stalls, funds can remain on the generated wallet. RemitFlex surfaces live sweep status on `GET /v1/rails/onramps/{id}` (`sweep`) and in the transactions receipt. Force completion with:

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/onramps/{id}/consolidate \
  -H "Authorization: Bearer $REMITFLEX_API_KEY"
```

That withdraws cNGN from the temporary onramp wallet into the customer's linked main Smart Wallet (`transfers:write`).

Consolidate is **not** marked complete from a StRails mined hash alone. RemitFlex re-checks the temporary wallet balance and on-chain Transfer verification:

| `sweep.consolidate.status` | Meaning                                                   |
| -------------------------- | --------------------------------------------------------- |
| `completed`                | Temp wallet cleared (or Transfer to main verified)        |
| `incomplete`               | Withdrawal returned but temp still holds cNGN — retryable |
| `submitted`                | Submitted; waiting for confirmation                       |
| `failed`                   | Withdrawal failed                                         |

Use `GET /v1/rails/onramps/{id}` → `sweep` to see temp/main balances and `canConsolidate` before retrying.

Response includes bank `accountNumber`, `accountName`, `bankName`, `payAmount`, and `vaExpiresAt` once StRails finishes async VA provisioning. Until then status stays **`requested`** (`virtualAccountStatus: pending_creation`); RemitFlex only sets **`va_created`** when an `accountNumber` is present. List with `GET /v1/rails/onramps`. Fetch one with `GET /v1/rails/onramps/{id}` (syncs StRails status on read; includes `sweep`).

Created onramps appear in the [transactions ledger](/products/transactions) as `rails_onramp`.

## Wallet swaps (cNGN ↔ USDC/USDT)

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/swaps \
  -H "Authorization: Bearer $REMITFLEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sellToken": "CNGN",
    "buyToken": "USDC",
    "amount": 5000,
    "customerId": "optional-customer-uuid"
  }'
```

| Field                    | Description                                        |
| ------------------------ | -------------------------------------------------- |
| `sellToken` / `buyToken` | `CNGN`, `USDC`, or `USDT` (must differ)            |
| `amount`                 | Human-readable amount of `sellToken`               |
| `slippage`               | Optional max slippage %                            |
| `smartWalletAddress`     | Optional override; defaults to user's Smart Wallet |
| `destinationAddress`     | Optional destination for output tokens             |
| `customerId`             | Optional; defaults to org primary customer         |

List with `GET /v1/rails/swaps`. Fetch one with `GET /v1/rails/swaps/{id}` (syncs StRails status on read).

There is no separate rails rate endpoint in this release — pricing is determined when you create the swap.

## Crypto withdrawal

Send tokens from the user's Smart Wallet to an external address:

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/withdrawals \
  -H "Authorization: Bearer $REMITFLEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "internalWallet": "0xUserSmartWallet",
    "destinationWallet": "0xExternal",
    "amount": 100,
    "ticker": "CNGN",
    "network": "base"
  }'
```

After `/withdrawasset`, RemitFlex stores the withdrawal as **`completed`** when StRails
returns a mined receipt (`transactionHash` plus `blockNumber` / `gasUsed` / `executedAt`).
On-chain ERC-20 `Transfer` verification (via Base RPC / `BASE_RPC_URL`) is best-effort
enrichment stored as `responseData.onChainVerification` — it is not required for
`completed` when StRails already confirmed execution. If the chain receipt shows a
revert and StRails did not return a mined receipt, status is `failed`.

## Bank directory & account name resolution

StRails offramps route on a **NIBSS bank code**, but StRails has no name-enquiry
endpoint. RemitFlex resolves destination account names via PayCrest
`verify-account`, so the offramp bank list is limited to banks supported by both
providers (matched by name). Use these to power a searchable bank picker with a
verified account name before submitting an offramp.

**List supported offramp banks** (`transfers:read`):

```bash theme={null}
curl https://api.remitflex.io/v1/rails/banks \
  -H "Authorization: Bearer $REMITFLEX_API_KEY"
```

```json theme={null}
{ "banks": [{ "name": "GTBank", "bankCode": "058" }], "total": 1 }
```

**Resolve an account name** (`transfers:read`):

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/offramps/resolve-account \
  -H "Authorization: Bearer $REMITFLEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountNumber": "0123456789", "bankCode": "058" }'
```

```json theme={null}
{ "accountName": "JOHN DOE", "bankName": "GTBank", "bankCode": "058" }
```

Returns `422` when the account cannot be verified or the bank is not supported.

## User bank offramp (cNGN → NGN)

```bash theme={null}
curl -X POST https://api.remitflex.io/v1/rails/offramps \
  -H "Authorization: Bearer $REMITFLEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "accountNumber": "0123456789",
    "bankCode": "058",
    "ticker": "CNGN"
  }'
```

`amount` is in **Naira** (same convention as StRails `/cngnofframp`). Resolve and
confirm the account name with `resolve-account` before creating the offramp —
`bankCode` must be a code returned by `GET /v1/rails/banks`.

## Status sync

Open rails swaps and offramps are refreshed by:

* StRails webhooks (`swap.*`, `vault.return.*`, `fintech.user.asset.transfer.*`, …)
* Background `rails-sync` job
* Sync-on-read for `GET .../swaps/:id` and `GET .../offramps/:id`

When `POST /v1/rails/offramps` times out or errors after StRails has already
accepted the payout, RemitFlex reconciles against recent StRails ledger entries
(matching amount, bank account, and user) and persists the offramp instead of
returning a hard failure. Status normalization treats StRails `payout.status:
completed` as **`completed`** even when the top-level status is still
`payout_pending`.

StRails enforces a minimum offramp amount of **₦151** (> ₦150). RemitFlex does
not cap the maximum beyond the customer's Smart Wallet cNGN balance.

Created rails swaps, withdrawals, user offramps, and temporary VA onramps also appear in the unified
[transactions ledger](/products/transactions) as `rails_swap`, `rails_withdrawal`,
`rails_offramp`, `rails_onramp`, and `rails_deposit` (VA onramps from StRails history).

## Activity history vs local records

| Source                                          | Endpoint                                              | Stored locally?                                                                                                               |
| ----------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| StRails ledger (VA deposits, onramps, offramps) | `GET /v1/customers/:id/strails/history`               | No — live from StRails `GET /transactions`                                                                                    |
| RemitFlex-initiated rails actions               | `GET /v1/rails/onramps`, offramps, swaps, withdrawals | Yes — `strails_rails_*` tables. Temporary VA deposits that match a local onramp are **not** double-listed as `rails_deposit`. |
| Unified merchant ledger                         | `GET /v1/transactions`                                | Yes — merges local rails rows with PayCrest, swaps, routes, etc.                                                              |

Onramps in StRails history expose `depositor` and `fundingAmount` (cNGN credited).
Offramps expose `bankAccount` (destination bank), `totalDeducted` (cNGN debited), and
`processingFee`. When StRails omits `bankAccount`, RemitFlex enriches from the local
offramp row matched by `requestId`.

## Fintech treasury offramp

Ops (JWT + platform-admin allowlist) can register payout banks and offramp the **fintech** Smart Wallet float:

* `POST /v1/admin/ops/treasury/bank-accounts`
* `POST /v1/admin/ops/treasury/offramps`

See the ops NGN treasury dashboard.
