For the complete documentation index, see llms.txt. This page is also available as Markdown.

Developer Controls (Client-Side Integration)

This guide is for developers building a client/front-end (custom web app, mobile app, or a modified HollaEx web UI) on top of the Fiat Controls that an operator configures in Operator Controls → Fiat Controls.

It explains how your client reads the operator's fiat configuration and how it drives the two user-facing flows described in the Fiat Controls overview:

  • Depositing (on-ramp): the user sees the exchange's bank/payment details, transfers funds out of band, and submits a deposit request with a payment reference. The operator later verifies it, and the fiat credits are minted.

  • Withdrawing (off-ramp): the user saves their own bank/payment details, then requests a withdrawal against one of those saved accounts. The operator transfers the funds, and the fiat credits are burned.

The actual movement of money happens out of band (bank to bank) and final approval is done by the operator. Your client's job is only to read the configuration, collect the right inputs, and submit the request — never to move funds itself.

This guide is client-side only. It does not cover operator/admin configuration APIs — use the in-app Operator Controls → Fiat Controls screens for that. All endpoints below are user-scoped and authenticated with the user's bearer token (never an admin/API key in a client app).


1. The three things the operator configures

In Operator Controls → Fiat Controls, the operator defines three pieces of configuration. Your client reads all three from the public kit config and uses them to render the UI.

Operator tab

Config key

What it is

Payment Accounts

user_payments

The catalog of payment method types and their fields (e.g. Bank with bank_name/iban, PayPal with an email field, or a fully Custom type). Shared schema referenced by both ramps.

On-Ramp

onramp

Per fiat asset, how users deposit — the bank/payment details shown to the user, or a payment-processor plugin.

Off-Ramp

offramp

Per fiat asset, which saved payment-account types are accepted for withdrawals.

The whole feature is gated behind the ultimate_fiat feature flag. If it is off, hide all fiat on-ramp/off-ramp UI.


2. Reading the configuration

Fetch the public kit config — it contains everything your client needs:

In the HollaEx web app these are already in the Redux store as state.app.onramp, state.app.offramp, state.app.user_payments, state.app.constants.fiat_fees, state.app.constants.features.ultimate_fiat, and state.app.coins.


3. Configuration data shapes

3.1 user_payments — the payment-type catalog

A dictionary keyed by payment-type name. Each entry lists the fields that make up an account of that type. These mirror the operator's Payment Accounts setup (Bank, PayPal, Custom + any "Add more payment details" custom fields with a Field name and a Required toggle).

  • The key (bank_transfer) is the canonical payment-type id referenced by offramp.

  • data is the ordered list of field definitions; each has at least a key (use it as the form field name) and typically label and required.

  • To render them in order, flatten to a sorted list:

3.2 onramp — deposit configuration

Nested currency → method name → method definition:

  • type: "manual" — show the user the exchange's deposit instructions (the detail rows in data), collect an amount + a payment reference, then submit a deposit request.

  • type: "plugin" — a third-party processor drives the flow; data is the plugin id you hand off to. (In the HollaEx web app this renders a SmartTarget with id generateDynamicTarget(data, 'ultimate_fiat', 'onramp').)

3.3 offramp — withdrawal configuration

currency → array of accepted payment-type keys (keys reference user_payments):

Meaning: "USD withdrawals are accepted via a bank_transfer or paypal account." Resolve each key against user_payments[key].data to know which fields the account needs.

3.4 The user's saved payment accounts (user.bank_account)

A user's concrete withdrawal accounts are stored as an array on their profile. Each entry:

  • Only verified accounts (status === 3) can be used to withdraw.

  • Read it from the authenticated user object (GET /user), available as state.user.userData.bank_account (or state.user.bank_account) in the web app.


4. Client endpoints

All endpoints are authenticated with the user's bearer token:

4.1 Submit a deposit (on-ramp)

POST /fiat/deposit

The response is a pending deposit awaiting operator verification. Reflect that "pending" state in your UI — the credits are not available until the operator approves it.

Server-side rules to mirror as client-side pre-checks:

  • The user must be verified (verification_level >= 1).

  • The user may have at most 3 pending deposits for a currency at once.

  • amount must be within the asset min/max and the user's deposit limit.

4.2 Submit a withdrawal (off-ramp)

POST /fiat/withdrawal

The response is a pending withdrawal (burn) awaiting operator processing.

Server-side rules to mirror:

  • bank_id must match one of the user's saved accounts, else "The selected payment option is not registered."

  • The user must be verified, not a sub-account, and within withdrawal limits.

  • At most 3 pending withdrawals per currency at a time.

  • Balance must cover amount + fee.

4.3 Manage the user's payment methods (/user/payment-details)

Use these to let users create and manage their fiat-control payment accounts. Flag fiat-control records with is_fiat_control: true.

Method

Path

Body / Query

GET

/user/payment-details

query: is_fiat_control, is_p2p, status, limit, page, order_by, order, start_date, end_date

POST

/user/payment-details

{ name, label?, details, is_p2p?, is_fiat_control?, status? }

PUT

/user/payment-details

{ id, name?, label?, details?, is_p2p?, is_fiat_control? }

DELETE

/user/payment-details

{ id }

A user cannot set or change status, and cannot edit a record once it has been verified (status === 3). Verification (raising status to 3) is done by the operator. Newly created methods start unverified and become usable for withdrawals only after the operator verifies them.

4.4 Fees & limits (helpers)

  • Fee: read from coins[currency].deposit_fees / coins[currency].withdrawal_fees (falling back to coins[currency].deposit_fee / withdrawal_fee). Prefer the operator override fiat_fees[currency].deposit_fee / .withdrawal_fee when present.

  • Limits: resolve from transaction_limits by matching limit_currency (the currency, else default), the user's verification_level, and type (deposit / withdrawal).

  • Min/max amount: coins[currency].min / coins[currency].max.

(The HollaEx web app wraps these as getFiatDepositFee, getFiatWithdrawalFee, getFiatDepositLimit, getFiatWithdrawalLimit.)


5. Building the deposit (on-ramp) UI

  1. Gate: require features.ultimate_fiat and coins[currency].type === 'fiat'.

  2. Require verification: if the user is not verified (verification_level < 1), prompt them to

    complete verification before depositing.

  3. Read onramp[currency]. If empty/undefined, show an empty state (no deposit method

    configured).

  4. Render one tab per method — iterate Object.entries(onramp[currency])[methodKey, { type, data }]:

    • type === 'manual': show the deposit instructions from data, plus the min/max/fee summary, then collect amount and transaction_id.

    • type === 'plugin': hand off to the processor identified by data.

  5. Submit: POST /fiat/deposit with { amount, transaction_id, currency, address: methodKey },

    then show the resulting pending state.

Reference implementation: web/src/containers/Deposit/Fiat/.


6. Building the withdrawal (off-ramp) UI

  1. Gate: same ultimate_fiat / fiat-currency / verification checks.

  2. Read offramp[currency] (array of accepted payment-type keys). If empty, no withdrawal

    method is configured.

  3. List the user's usable accounts: take the user's verified accounts

    (user.bank_account.filter(a => a.status === 3)) and keep those whose type is in offramp[currency]. If the user has no verified account of an accepted type, prompt them to add one (Section 4.3) and have it verified.

  4. Collect input: let the user pick an account and enter an amount; show the fee and limit and

    ensure amount + fee <= balance.

  5. Submit: POST /fiat/withdrawal with { amount, bank_id: account.id, currency }, then show

    the pending state.

Reference implementation: web/src/containers/Withdraw/Fiat/ and web/src/containers/Wallet/AddressBook.js.


7. Quick reference

You need to…

Do this

Detect if fiat is enabled

GET /kitfeatures.ultimate_fiat

Read deposit / withdrawal config

GET /kitonramp[currency] / offramp[currency]

Read payment-type fields

GET /kituser_payments[type].data

Read the user's saved accounts

GET /userbank_account (verified = status === 3)

Submit a fiat deposit

POST /fiat/deposit { amount, transaction_id, currency, address }

Submit a fiat withdrawal

POST /fiat/withdrawal { amount, bank_id, currency }

List / create / edit / delete payment methods

GET / POST / PUT / DELETE /user/payment-details (is_fiat_control: true)

Show fees / limits

coins[currency] + fiat_fees[currency] + transaction_limits

Reference front-end source

Area

File

Reading config into the store

web/src/actions/appActions.js (setConfig)

Deposit (on-ramp) UI

web/src/containers/Deposit/Fiat/

Withdrawal (off-ramp) UI

web/src/containers/Withdraw/Fiat/

Saved payment accounts UI

web/src/containers/Wallet/AddressBook.js

Fee / limit helpers

web/src/containers/Deposit/Fiat/utils.js, web/src/containers/Withdraw/Fiat/utils.js

Last updated