What the platform earns, and referral rewards

Reading the addon's own side of the book — the three admin-profit entry types it writes, why a customer WIN is recorded as a platform loss, the two fee legs, and the affiliate condition settlement fires.

7 min readUpdated 6 August 2026profit, adminprofit, fees, affiliate, reconciliation

Forex Investment is a book where a customer's win is your loss. The overview says the platform's own side of every settled investment is recorded; this page says where that record is, what each entry means, and the three ways of reading it that produce a number that does not exist.

Everything below lands in one table, admin_profit, read at Admin → Finance → Revenue Analytics (/admin/finance/profit), gated on access.admin.profit.

The three entry types

Type Written when Sign Booked against
FOREX_DEPOSIT A customer funds a forex account, in the same transaction as the debit Positive — a fee you collected The wallet debit's transaction id
FOREX_WITHDRAW An admin approves a withdrawal. Never on rejection Positive The pending transaction's id
FOREX_INVESTMENT Settlement, in the same transaction as the payout Either — see below {investmentId}_payout on a win, {investmentId}_house on a loss

Filter the Type column on /admin/finance/profit to any of the three. The row itself carries type, amount, currency, chain, description, createdAt and a link to the transaction that produced it.

A WIN is recorded as a loss, on purpose

Settlement calls one helper, recordInvestmentOutcome, and it inverts the sign deliberately:

Result What the platform did What is recorded
WIN Paid the customer a profit it has to fund A negative FOREX_INVESTMENT row for the ROI amount, plus a best-effort debit of the Super Admin treasury
LOSS Kept the slice of principal the customer forfeited A positive FOREX_INVESTMENT row for the same magnitude, credited to the Super Admin treasury
DRAW Nothing moved No row at all

The amount on the row is the ROI magnitude, not the principal. A 1,000 principal on a plan paying 15% that settles WIN writes -150, not -1,150 and not -1,000.

The wallet movement behind a WIN and the one behind a LOSS cannot share a reference. transaction.referenceId is unique platform-wide, so reusing one investment id for both legs would collide — which is why the helper appends _payout on the payout leg and _house on the retained-principal leg before handing the id down.

The wallet layer then adds one more suffix of its own: the treasury debit lands under {investmentId}_payout_loss, the treasury credit under {investmentId}_house_fee. The idempotency keys behind them are platform_loss_FOREX_INVESTMENT_{investmentId}_payout and platform_fee_FOREX_INVESTMENT_{investmentId}_house, which is what makes a re-run of the cron pay nothing twice. The admin_profit row itself links to the movement by transactionId, so you only need these references when that link is NULL and you are searching the transaction table by hand.

Two things that follow from the payout leg being best-effort:

  • The negative row is always written, even when the treasury cannot cover the payout, so your profit report nets out correctly regardless. The wallet debit is capped at the Super Admin wallet's actual balance.
  • A short treasury writes a row with no transaction behind it. transactionId is NULL on that row, the backend log carries Treasury short on FOREX_INVESTMENT payout or Recorded FOREX_INVESTMENT loss ... with NO treasury debit under PLATFORM_FEE, and nothing on the admin screen flags it. The row is the only evidence the payout happened.

The wallet type on the house side of a settlement is fixed at FIAT, regardless of the plan's own wallet type. On a platform whose plans are denominated SPOT USDT, the Super Admin's FIAT USDT wallet is the one credited on a customer loss and debited on a customer win — not the SPOT wallet their fees land in.

The admin_profit row is correct either way; only the wallet the money passes through is affected. Fund the FIAT wallet in each plan currency you run, or the payout leg will be recorded without a matching treasury debit indefinitely.

The currency stamped on a FOREX_INVESTMENT row is the forex account's bound currency, falling back to the plan's currency, falling back to USD when the account has never been funded. On a single-denomination install those are all the same value; on a mixed one they are not.

Reading the figures without inventing a number

admin_profit.currency is a per-row label, and nothing above the row groups on it consistently:

  • Top fee sources — grouped by type only. The number beside FOREX INVESTMENT is a bare SUM(amount) across every currency on the platform, rendered without a symbol. On a mixed-currency book it is meaningless.
  • Top currencies — grouped by currency, across every type. Valid as a currency total, useless as a forex figure.
  • Today / This week / This month — grouped by currency. Valid.

The only honest per-product read is the table itself: filter Type to one of the three forex values, then read one currency at a time. If you are querying directly, GROUP BY type, currency is the minimum.

amount is DECIMAL(36,18), so it is exact — unlike forex_investment.amount, which is a DOUBLE. Do not reconcile one against the other without rounding deliberately.

The two fee legs

Neither fee is configured anywhere in this addon. Both come from core's currency tables.

Leg Where the fee comes from When it is collected
Deposit FIAT: zero, always — the fiat currency table has no fee column. SPOT: the percentage on the exchange_currency row, plus the network's fixed withdrawal fee when a chain is named and your provider is Binance or KuCoin Immediately, in the same transaction as the wallet debit
Withdrawal The same calculation, quoted and debited at submission On approval only. Rejecting returns amount plus fee to the forex account and collects nothing

A deposit reversal returns the amount and the fee to the customer's wallet. It does not write a compensating admin_profit row, so a reversed deposit leaves its original FOREX_DEPOSIT fee row standing. Account for that when reconciling a month with reversals in it.

The full behaviour of both queues is in Deposits and withdrawals.

The affiliate reward

One condition in this addon pays a referrer, and it fires from the settlement cron.

Condition name FOREX_INVESTMENT
Fires Once per investment, at settlement, before the completion email
Amount passed to the engine The principal — not the profit, not the payout
Currency passed The plan's currency
Deduplication id FOREX_INVESTMENT:forex_investment:{investmentId}
Requires The mlm extension installed and enabled. Without it the call returns immediately

Because the principal is what is passed, a PERCENTAGE reward pays a percentage of the amount invested, and the condition's minimum amount is compared against the principal in the plan's own currency with no conversion. A minimum of 100 against a BTC-denominated plan can never be met.

Set it at Admin → Affiliate → Program → Conditions (/admin/affiliate/condition), gated on view.affiliate.condition and edit.affiliate.condition. The editable fields are title, description, reward, rewardType (FIXED or PERCENTAGE), rewardWalletType, rewardCurrency, rewardChain, minAmount, image and status. A save is refused if a PERCENTAGE reward exceeds the Maximum Commission Rate ceiling on the affiliate settings screen — a FIXED reward is an absolute amount rather than a percentage, so that ceiling never applies to it, at any size — and turning a condition on is refused when it competes with one already active unless you acknowledge the overlap.

Most affiliate conditions are seeded off. This one is not. A fresh install seeds FOREX_INVESTMENT active, with rewardType FIXED, reward 100, minAmount 100, rewardWalletType SPOT and rewardCurrency USDT.

That means the first referred customer whose 100-unit investment settles earns their referrer a flat 100 USDT reward — larger than the investment on most plans. Open the Conditions screen and set it deliberately before you open the product, or turn it off.

Existing installs are untouched: the seeder skips by name, so whatever you already chose stands.

The reward creates a reward record for the referrer, writes them a Referral Reward Earned notification, and notifies admins holding View MLM Rewards. It does not credit a wallet directly — payout follows the affiliate addon's own rules. See Commission conditions.

Reward processing sits in its own try/catch in the cron's post-processing, after the settlement transaction has committed. It cannot block or reverse a settlement, and a failure produces no error anywhere an operator looks — only a line under FOREX_INVESTMENT_POST_PROCESS in the backend log, or under MLM when the condition itself is missing or inactive.

A referrer reporting a missing reward is a log question, never a screen question.

Two condition names that do nothing

The affiliate engine still accepts FOREX_PROFIT and FOREX_TRADE — they are listed among the condition names it recognises, and the affiliate screens group them under the forex product — but nothing in this addon fires either. FOREX_PROFIT is seeded disabled; FOREX_TRADE is not seeded at all.

Enabling FOREX_PROFIT changes nothing and pays nobody. Its title, Forex Profit Commission, describes a payout on investment profit that this addon has never had a code path for. If you want a percentage of profit paid out, the only working lever is FOREX_INVESTMENT with a PERCENTAGE reward — which pays on the principal, not the profit.

The FX_TRADE_COMMISSION and FX_TRADE_VOLUME conditions belong to Forex & Multi-Asset Trading and are unrelated to anything on this page.

A monthly reconciliation that works

  1. Open Revenue Analytics (/admin/finance/profit) and filter the Type column to FOREX_INVESTMENT. Read one currency at a time — the summary cards above the table will not do this for you.

  2. Net the signs. Negative rows are payouts you funded; positive rows are forfeited principal you kept. The sum per currency is the addon's real contribution, and it can legitimately be negative.

  3. Add the two fee types. FOREX_DEPOSIT and FOREX_WITHDRAW are always positive, and always in the currency the customer moved.

  4. Check the log for PLATFORM_FEE over the same window. Rows recorded against an empty treasury, and fees dropped because no Super Admin user exists, are logged there and nowhere else.

  5. Cross-check against the forex dashboard's per-currency profit rows. They are computed from forex_investment.profit and answer a different question — the customers' side. The two move in opposite directions by design.

If the two disagree by the size of one settlement, the usual cause is a settlement whose plan was soft-deleted: the investment is skipped and left ACTIVE, so it appears in neither book. See Troubleshooting.