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
user_payments — the payment-type catalogA 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 byofframp.datais the ordered list of field definitions; each has at least akey(use it as the form field name) and typicallylabelandrequired.To render them in order, flatten to a sorted list:
3.2 onramp — deposit configuration
onramp — deposit configurationNested currency → method name → method definition:
type: "manual"— show the user the exchange's deposit instructions (the detail rows indata), collect an amount + a payment reference, then submit a deposit request.type: "plugin"— a third-party processor drives the flow;datais the plugin id you hand off to. (In the HollaEx web app this renders aSmartTargetwith idgenerateDynamicTarget(data, 'ultimate_fiat', 'onramp').)
3.3 offramp — withdrawal configuration
offramp — withdrawal configurationcurrency → 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)
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 asstate.user.userData.bank_account(orstate.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.
amountmust 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_idmust 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)
/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 tocoins[currency].deposit_fee/withdrawal_fee). Prefer the operator overridefiat_fees[currency].deposit_fee/.withdrawal_feewhen present.Limits: resolve from
transaction_limitsby matchinglimit_currency(the currency, elsedefault), the user'sverification_level, andtype(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
Gate: require
features.ultimate_fiatandcoins[currency].type === 'fiat'.Require verification: if the user is not verified (
verification_level < 1), prompt them tocomplete verification before depositing.
Read
onramp[currency]. If empty/undefined, show an empty state (no deposit methodconfigured).
Render one tab per method — iterate
Object.entries(onramp[currency])→[methodKey, { type, data }]:type === 'manual': show the deposit instructions fromdata, plus the min/max/fee summary, then collectamountandtransaction_id.type === 'plugin': hand off to the processor identified bydata.
Submit:
POST /fiat/depositwith{ 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
Gate: same
ultimate_fiat/ fiat-currency / verification checks.Read
offramp[currency](array of accepted payment-type keys). If empty, no withdrawalmethod is configured.
List the user's usable accounts: take the user's verified accounts
(
user.bank_account.filter(a => a.status === 3)) and keep those whosetypeis inofframp[currency]. If the user has no verified account of an accepted type, prompt them to add one (Section 4.3) and have it verified.Collect input: let the user pick an account and enter an
amount; show the fee and limit andensure
amount + fee <= balance.Submit:
POST /fiat/withdrawalwith{ amount, bank_id: account.id, currency }, then showthe 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 /kit → features.ultimate_fiat
Read deposit / withdrawal config
GET /kit → onramp[currency] / offramp[currency]
Read payment-type fields
GET /kit → user_payments[type].data
Read the user's saved accounts
GET /user → bank_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