Accounts
How forex accounts work — DEMO versus LIVE, the pre-provisioned account pool, MetaTrader credentials and the embedded terminal, the currency binding that cannot be undone, and the per-account withdrawal caps.
A forex account is a balance the platform holds on the customer's behalf, plus a set of MetaTrader credentials you supply. It is the only place an investment can be funded from and the only place a payout can land, so everything else in the product depends on getting it right.
DEMO and LIVE
Every customer ends up with exactly two accounts, one of each type. Both are
created automatically the first time they open /forex/dashboard.
| DEMO | LIVE | |
|---|---|---|
| Balance | A number on a row. Nothing backs it | Real money, funded from the customer's wallet |
| Deposits and withdrawals | Refused | Allowed |
| Investments | Cannot fund one | The only source of principal |
| KYC gate on provisioning | None | create_forex_account |
The deposit, withdraw and investment routes all assert the account type before touching a balance. This used to be checked only by the React page, so a customer could post directly to a demo account's withdraw endpoint: the paper balance was debited, and on approval the platform paid out real funds against it. On rejection the refund landed on the LIVE account, turning paper money into withdrawable balance.
The two accounts are also the reason there is no open an account button. The customer's first visit to the dashboard provisions whatever they are missing.
How provisioning works
When /forex/dashboard loads, the platform looks at the accounts the customer
holds and fills in the missing types. For each one it does one of two things:
-
Claim a pre-provisioned account. If a row of that type exists with no owner, it is assigned to the customer and switched to active. This is the path you want, because that row can already carry the broker, the MetaTrader version, a real account ID and a password.
-
Create an empty one. If no unassigned row exists, a new account is created with a zero balance and status inactive, and no broker details at all.
That second branch is what most installs hit, and it produces an account the customer can fund and invest with but cannot actually trade on — there is no broker, no login and no password to give the terminal.
Create rows at /admin/forex/account ahead of demand, leaving the owner
unset, with the broker server name, MetaTrader version, account ID and password
already filled in from your broker's back office. Customers claim them on first
visit and get working credentials immediately.
Keep the pool stocked. When it runs dry, new customers silently fall back to empty accounts.
The LIVE branch is the only one gated: a customer whose verification level does
not carry create_forex_account simply does not get a LIVE account
provisioned. Their DEMO account and any account they already hold still load —
the gate never withholds the whole dashboard, so tightening your levels cannot
lock an existing customer out of their own balance.
Account fields
Admin → Forex → Accounts (/admin/forex/account). The form has four
groups.
| Field | Notes |
|---|---|
| Owner | Required on the admin create form. Leave it unset in the database to build a claimable pool |
accountId |
The broker login. Passed to the web terminal as login |
password |
The account password. Shown to the customer on the terminal screen |
broker |
The broker's server name, passed to the terminal as servers |
mt |
4 or 5. Decides which terminal the customer is sent to |
type |
DEMO or LIVE |
balance |
The account balance. Optional on create; defaults to 0 |
leverage |
An integer, defaults to 1. Stored and displayed only |
status |
Active or inactive |
Creating a second LIVE account for a customer who already has one is refused with a 409. Every lookup in the addon reads this user's LIVE account with no ordering, so a duplicate makes it arbitrary which row gets debited, credited or refunded — a customer could see a balance on one row while an investment was refused for insufficient funds against the other, and a cron refund could land on a different account from the one the principal was taken from.
The MetaTrader terminal
/forex/trade/{id} embeds the public MetaTrader WebTerminal in an iframe and
shows the customer their credentials alongside it. The URL is built from the
account row:
mt |
Terminal |
|---|---|
5 |
trade.mql5.com |
| anything else | metatraderweb.app |
Both receive servers= from the broker field and login= from accountId.
No balance, position or trade is ever read back from MetaTrader. The terminal
is a hosted third-party page the customer signs into with the credentials you
issued. The balance column on the forex account is the platform's own ledger
for investments, deposits and withdrawals — it has nothing to do with what the
broker account is worth.
If the terminal will not connect, the broker server name is wrong. It must be the server string the broker publishes, exactly as MetaTrader expects it.
The currency binding
A forex account holds a single balance number, so it must mean one thing. The first deposit or withdrawal on an account locks it to that currency and wallet type, and every later movement has to agree.
Before the binding existed, the balance was a bare number every currency shared: depositing 1,000 USDT and withdrawing 0.5 BTC against "the same balance" handed the customer 32,000 BTC. The account worked as a free converter at 1:1.
Once bound, an attempt to move a different currency is refused with a message
telling the customer to withdraw the remaining balance first. There is no admin
button to rebind an account. If a customer genuinely needs a different
denomination, they must empty the account, and you must clear currency and
walletType on the row directly.
Accounts created before the binding existed have no currency yet. The first movement after upgrading sets it — the same rule new accounts follow.
The binding also gates investments. An account bound to SPOT USDT can only
invest in plans denominated in SPOT USDT; anything else is refused with a
message naming both denominations. In practice this means running one
denomination across your whole plan catalogue unless you have a specific reason
not to.
Withdrawal caps
Two rolling caps live on each account row, and both are editable per account.
Four things about how they behave:
- They count the gross figure — amount plus fee — which is the same figure validated when the customer submits. What was checked and what is counted can never disagree.
- They advance on approval, not submission. Counting a withdrawal that is later rejected would silently burn the customer's allowance.
- The windows are independent. Daily rolls 24 hours after its own anchor, monthly 30 days after its own. They used to share one anchor, which meant the monthly counter only reset after thirty consecutive days with no withdrawals at all — an active customer hit the monthly cap and was then locked out permanently, unable to withdraw and unable to have pending requests approved.
- The approval side re-checks. Approving a withdrawal that would breach the cap is refused, so a queue of pending requests cannot be cleared past the limit in a batch.
The defaults read like dollar figures and were written as such, but the comparison is against the raw amount in whatever currency the account is bound to. On a BTC-denominated account, a daily limit of 5,000 means five thousand BTC — no cap at all in practice.
Set both limits deliberately for every denomination you run.
Editing and deleting accounts
The accounts table supports create, edit, delete and a status toggle, plus bulk status changes. Editing an account with a fractional balance works — an earlier build failed validation on a field the admin had not touched, and the only way to save was to round the balance and destroy the remainder.
Editing the balance directly is a manual adjustment with no transaction row behind it. Prefer reversing a deposit or rejecting a withdrawal, both of which leave an audit trail, and keep direct balance edits for genuine corrections.
Deleting an account does not clean up after it. Investments reference the
customer, not the account, and settlement then looks for this user's LIVE
account and finds nothing — the payout is skipped and logged, and the
investment stays ACTIVE. Settle or cancel a customer's investments before
removing their account.
Next: Investments — what happens between the principal leaving this account and the payout coming back to it.