The investment lifecycle
What happens between the debit and the payout — the four statuses, the terms snapshot, the exact settlement arithmetic, cancellation, and the two doors that can settle the same investment.
An AI investment has four statuses and only one of them is not terminal.
| Status | Meaning | Money position |
|---|---|---|
ACTIVE |
Running. The term has not expired, or has expired and has not yet been settled. | Principal is out of the user's wallet |
COMPLETED |
Settled at maturity. | Payout credited |
CANCELLED |
Ended early — by the user, or by an administrator. | Full principal refunded |
REJECTED |
Ended by an administrator, or rejected by settlement as unsettleable. | Full principal refunded via the admin door; see the caveat below |
ACTIVE is the default on creation. Nothing else can be set at creation through
the user endpoint.
Opening an investment
The purchase panel posts the plan, the duration, the amount, the market's base and quote currency, and the wallet type. The route runs these checks in order, and any one of them refuses the whole request:
-
The caller is signed in, and passes the
invest_aiKYC feature gate if feature enforcement is on. -
The plan exists and is active. A deactivated plan is refused even if the user still has its id.
-
The duration exists and is joined to that plan. The join is the contract — a duration that exists but is not attached to this plan is refused.
-
The amount is within the plan's limits, inclusive at both ends.
-
A wallet exists for this user, of the requested
type, in the market's quote currency. OnBTC/USDTthat is the USDT wallet, not the BTC one. The row is locked for update. -
The balance covers the amount.
Only then does anything change. Inside one database transaction the addon creates the investment row and debits the wallet, so a failure at either step leaves nothing behind.
What is written on the row
symbol BTC/USDT base/quote, from the market being viewed
type SPOT or ECO which wallet the principal came from
amount 1000 in the QUOTE currency
status ACTIVE
roiPercentage 5 snapshot of plan.profitPercentage
result WIN snapshot of plan.defaultResultroiPercentage and result are copied from the plan at purchase and are never
re-read from the plan afterwards. Before this existed, settlement read the plan
hours or days later — so an administrator changing a rate or an outcome silently
rewrote the terms of every investment already running, and a user could be quoted
one figure at purchase and paid another.
The snapshot is a server-side integrity guarantee. It is not something the
investor may see: while an investment is ACTIVE, the result, roiPercentage
and profit fields are nulled out of every user-facing response, because
otherwise anyone with the network tab open could read the outcome days before it
was due.
The debit is written as an AI_INVESTMENT transaction whose referenceId is the
investment's own id, under the idempotency key investment_<id>. That
transaction is the link back to the wallet the principal came from, and
settlement uses it to decide where the payout goes.
After the transaction commits, three best-effort things happen — a
NewAiInvestmentCreated email, an in-app notification, and the AI_INVESTMENT
affiliate reward for the referrer. All three are wrapped so a failure is logged
and never rolls back the investment.
While it runs
Nothing. No price is read, no position exists, no value moves. The row sits at
ACTIVE until its term expires.
The user sees it in the Orders panel of the Pro trade workspace, showing the plan, the amount, the symbol and a dash where the result will be.
Settlement
Two things settle an investment automatically, and they run the identical code:
- The hourly cron,
processAiInvestments, which scans everyACTIVEinvestment on the install. - The user's own read endpoints. Listing investments or opening one runs settlement in-line for the caller's matured investments before returning.
The in-line path exists so a user who opens the tab is paid immediately rather than waiting for the top of the hour. Its errors are swallowed on purpose, so a settlement that cannot complete never blocks the page — which also means it will never tell you something is wrong.
Deriving the terms
Settlement resolves the ROI percentage from the first of these that is present:
-
roiPercentageon the investment — the snapshot taken at purchase. This is the normal path for anything created since 6.1.1. -
profiton the investment — a deprecated column holding an absolute ROI amount, not a percentage. Used only by legacy rows. Reading it as a percentage was a real defect: aprofitof 500 on a 1,000 principal became a 500% payout. -
The plan's
profitPercentage, falling back to itsdefaultProfit. Used by pre-snapshot rows whose plan still exists. -
Zero. The plan is gone and nothing was recorded on the investment. No profit figure can be honestly derived from a plan that no longer exists.
The outcome is resolved the same way: the snapshot on the investment, then the
plan's defaultResult, then DRAW.
DRAW is the conservative last resort. It hands the principal back and invents
nothing, which is the only defensible answer when the plan that defined the
outcome has been deleted. This precedence lives in one shared module used by both
the cron and the admin door, because the two once disagreed on this last step —
one fell back to DRAW and the other to WIN, so the same orphaned investment
paid a different amount depending on which button was pressed.
The payout
With amount as the principal and roi as amount × roiPercentage / 100:
| Result | Credited to the wallet |
|---|---|
WIN |
amount + roi |
LOSS |
amount - roi, floored at 0 |
DRAW |
amount |
A LOSS plan configured to take more than the whole principal cannot make a user
owe you money. The floor at zero means the payout becomes nothing and no
transaction is written at all.
The credit is an AI_INVESTMENT_ROI transaction with referenceId
<id>_roi. The investment is then updated to COMPLETED, with result,
roiPercentage and the absolute profit all written for later readers.
Where the payout goes
Settlement resolves the destination wallet in two attempts:
- The wallet referenced by the original
AI_INVESTMENTfunding transaction. - Failing that, a wallet matching the user, the investment's
type, and the quote half of itssymbol.
If neither resolves, the settlement is rolled back, an ALERT is written to the
log naming the investment, the user, the symbol and whether the funding
transaction was found, and the investment is left ACTIVE for manual review.
It is deliberately not marked REJECTED: the principal was really debited, and
writing a terminal status without a refund would strand the money silently.
The one case that does write REJECTED from settlement is an investment whose
amount is null or not positive. That row could not have taken any money in the
first place.
The platform's own side
Every settlement books the platform's leg as well as the user's:
- A
WINrecords the profit paid out as a platform loss, under reference<id>_payout. - A
LOSScredits the forfeited amount to the Super Admin's wallet and records it as platform revenue, under reference<id>_house. - A
DRAWrecords nothing. Nothing changed hands beyond the principal.
This is reporting, not a second payment to the user, and it never throws — a failure here cannot strand a settled investment. Settlements from before the 6.1.1 release are not backfilled, so the figures start from the day you upgraded. Note also that the AI Investment dashboard's own profit figure is computed from the investments themselves and is a separate number from platform profit and loss.
Cancellation
A user may cancel any ACTIVE investment from the Orders panel. There is no
early-exit fee, no penalty and no partial return — the full principal goes back.
The refund is written as a REFUND transaction with referenceId
<id>_refund. The suffix matters: transaction.referenceId carries a
platform-wide unique index and the funding debit already claimed the bare
investment id, so an earlier build's refund failed that constraint and made
cancellation impossible for every investment on every install.
The investment is marked CANCELLED and then soft-deleted, in that order. The
original funding transaction is preserved and annotated with a cancelled flag,
a timestamp and the id of the refund — deleting it would leave a refund in the
ledger with no matching charge.
A cancel arriving at the same moment as the maturity payout would refund a principal that had already been returned. Both paths re-read the investment under a row lock inside their transaction, so whichever commits second sees the other's status and stops. The user cannot be credited twice.
No email is sent on cancellation. The AiInvestmentCanceled template is seeded
and the email helper accepts it, but nothing calls it.
Administrative settlement
An administrator can end an ACTIVE investment from the Investment Logs table,
either one at a time or in bulk. Both routes run the same shared settlement
authority as the cron.
| Action | Effect |
|---|---|
| Completed | Settles on the derived terms, exactly as maturity would — including the platform's own leg. Ignores whether the term has expired. |
| Cancelled | Refunds the full principal as a REFUND transaction. |
| Rejected | Identical to Cancelled: full principal refunded. |
Three transitions are refused outright: setting a status the investment already
has, changing anything that is not currently ACTIVE, and — through the edit
form rather than the status action — writing status as a plain column.
That last refusal is the important one. There were once two status doors and only
one of them paid anything; the bulk door flipped ACTIVE investments to
COMPLETED with no payout, the cron then skipped them forever because it only
scans ACTIVE rows, and the record said the investment had been completed while
the user had been paid nothing. Both doors now go through the same code, and the
bulk action reports per-investment successes and failures rather than claiming a
batch succeeded when none of it moved.
Completing a one-day-old investment on a 12 MONTH term pays the full profit immediately. There is no pro-rating anywhere in this product. If you are using the status action to clear a stuck row, check what it is going to pay first.
Deleting an investment
Deleting from the Investment Logs table refunds the principal only when it is
genuinely still outstanding — that is, when the investment is ACTIVE and has
not already been soft-deleted by the user's own cancel. A COMPLETED investment
has already been paid, a CANCELLED one has already been refunded, and neither
gets a second credit.
The refund uses reference <id>_admin_refund, and the funding transaction is
preserved rather than destroyed.
Restore genuinely restores. The table is paranoid, so a deleted investment can be brought back, and the restore action no longer runs the delete path — an earlier build permanently destroyed the record and issued another refund when you pressed Restore on an already-cancelled investment.
Why a payout can never happen twice
Four independent mechanisms, worth knowing because they are what makes retrying safe:
Settlement re-reads the investment inside its transaction with a FOR UPDATE
lock and abandons the run if the status is no longer ACTIVE. Two concurrent
settlements of the same investment serialise, and the second one stops.
Every wallet movement carries a key. The cron payout uses
ai_invest_cron_payout_<id>_<RESULT> — the outcome is part of the key because
the amount varies by outcome — the admin payout uses
ai_invest_admin_payout_<id>, the user refund investment_refund_<id>, and the
admin refund ai_invest_admin_<status>_<id>.
transaction.referenceId is unique platform-wide, and every payout for an
investment writes <id>_roi regardless of which door produced it. A second
payout collides on that index even when the idempotency keys differ.
A collision on either the idempotency key or the reference is read as "this ROI
was already paid". The settlement does not pay again and does not roll back:
it commits the terminal COMPLETED status so the investment stops looping
through every hourly run, and suppresses the completion email so nobody is told
twice about a single payout.
Next: Admin screens — where all of this is visible, and which buttons move money.