The ICO admin dashboard
The console at /admin/ico — its two permissions, the aged review queue and what it does not weigh, the four stat cards, the status distribution and the activity feed.
Admin → ICO → Dashboard (/admin/ico) is where you decide what to work on.
It answers one question above everything else — is anything waiting on me, and
for how long — and then gives you the totals and the drill-throughs to act on
the answer.
Every figure on it is a server-side aggregate over the whole table, not a sample of the first page of rows. Two things on the page are not totals, and both say so on screen: Total Raised, which is not currency-converted, and the activity feed, which is a capped page of the audit trail.
Two permissions, and they do different jobs
| Key | Controls |
|---|---|
access.ico |
Opening /admin/ico at all. Enforced by the route middleware. |
access.ico.stat |
The aggregate behind every number on it. |
The two are checked in different places, so granting one without the other is a state you can reach and will not be warned about. The page opens, the stat request is refused, no payload ever lands — and because a console showing four zeros would be asserting something it cannot know, the body is replaced by a Dashboard unavailable panel carrying the error, a Try again button and a link to the offerings queue.
The masthead, its Refresh button and Create Offer survive that branch, so an operator in this state can still reach the rest of the addon. Grant both keys together.
Create Offer in the header goes to /admin/ico/offer/create and needs
create.ico.offer of its own. See
Endpoints, permissions and keys for all
fifteen.
The masthead
Three tiers, top to bottom: where the figures came from, the page title, and the review queue.
The first line is provenance and it is literal. Live with a pulsing dot means the console is polling — it re-reads the whole aggregate every 30 seconds, and only while the tab is visible, so a queue that breaches its budget while you have the tab open appears without a reload. Returning to a backgrounded tab refreshes immediately. If a poll fails, the dot goes amber and the word changes to Stale, and the last-updated clock stops advancing rather than lying about a refresh that did not land.
The review queue, and the one thing it does not weigh
The band under the title is the pending queue split by how long each offering has waited. The budget is the platform's shared approval SLA — 72 hours — which is the same number the offerings queue and the platform operations inbox judge the same rows against, so the three cannot disagree about whether an offering is late.
| Band | Meaning |
|---|---|
| On time | Waiting under 36 hours |
| Due | Waiting 36 to 72 hours — half the budget gone |
| Overdue | Waiting 72 hours or more |
Beside them, Oldest waiting is the age of the single oldest undecided
offering, computed from MIN(COALESCE(submittedAt, createdAt)) across every
PENDING row. submittedAt is the moment the creator handed the offering over;
createdAt is the fallback for rows written before that column was populated.
Every other risk figure on this platform is weighted by money. This one is not. A stalled review of a project targeting ten million and a review of a hobby token are one offering each, and they move the meter by exactly the same amount.
That is deliberate — a PENDING offering has raised nothing by definition, so
the unit here is decisions, not exposure. But it means the dashboard cannot tell
you which review is expensive to get wrong. For money-weighted exposure on a
specific offering, open its detail page and read the funding series
(GET /api/admin/ico/offer/[id]/funding), which returns valid and rejected
funding day by day with running cumulative totals.
One more honesty note about the meter: a band holding a single offering in a large queue would round to a sub-pixel sliver, so any non-empty band is widened to a visible minimum and the width is taken back from the widest band. The segment widths are therefore not exact proportions. Read the numbers in the legend, not the bar.
Review now beside the meter, and the Awaiting Review tile below it, both
land on the same place: /admin/ico/offer/status/pending. When anything is past
the budget, an amber banner appears above the tiles naming the count, the budget
and the age of the oldest, with the same button on it.
The four tiles
Each one is a live whole-table figure, and each one clicks through to the rows that produced it.
| Tile | What it counts | Opens |
|---|---|---|
| Awaiting Review | Offerings at PENDING |
/admin/ico/offer/status/pending |
| Live Offerings | Offerings at ACTIVE |
/admin/ico/offer/status/active |
| Total Raised | SUM(price × amount) over every contribution that is not REJECTED |
/admin/ico/transaction |
| Total Offerings | COUNT(*) over the offerings table |
/admin/ico/offer |
Two separate qualifications, both of which the tile states on screen and both of which are easy to skim past.
It includes contributions that are not settled. The sum spans every status
except REJECTED, which means it carries PENDING intents and REFUNDED
money. The caption underneath names the RELEASED subset — the part that has
actually left escrow for a creator — and that is the figure to quote.
It is not currency-converted. Each offering carries its own
purchaseWalletCurrency, and the sum adds them together untouched. When your
install has exactly one purchase currency in use across offerings that got past
review, the tile prints the symbol. When it has more than one, the sum is a
number with no unit, the symbol is dropped, and the caption reads summed across
N currencies instead. Do not put a $ on it yourself.
The from last month chips on Total Raised and Total Offerings are
month-over-month, comparing this calendar month so far against the whole of the
previous one, on UTC month boundaries. A previous month with nothing in it
renders as 0% rather than as infinite growth.
The status distribution
The left-hand card breaks the offerings table down by status. Four named rows and a remainder.
| Row | Status | Opens |
|---|---|---|
| Active | ACTIVE |
/admin/ico/offer/status/active |
| Pending | PENDING |
/admin/ico/offer/status/pending |
| Completed | SUCCESS |
/admin/ico/offer/status/success |
| Rejected | REJECTED |
/admin/ico/offer/status/rejected |
| Other | everything else | nothing — it is several statuses |
The row is labelled Completed and the status is SUCCESS. The URL follows the
status, not the label, so /admin/ico/offer/status/success is the page and
/admin/ico/offer/status/completed matches nothing at all — it renders an empty
table rather than an error.
The offerings enum has eight members. UPCOMING and DISABLED exist in the
schema and in the offer filters but nothing ever writes them: approving an
offering makes it ACTIVE immediately, even when its start date is weeks out,
and the purchase route is what refuses early contributions. Do not wait for an
"Upcoming" bucket to fill itself.
The Other row is the reason the four named bars do not add up to Total
Offerings. It is the count of everything the aggregate does not name —
FAILED, CANCELLED, and any UPCOMING or DISABLED row that predates the
current code or was set by hand in the database. It is shown rather than hidden
so the distribution cannot quietly misrepresent the size of the table, and it is
the one row with no drill-through, because it is not one status.
The per-status pages and the offering detail page
/admin/ico/offer/status/<status> is the offerings table filtered to one value.
The segment is capitalised and matched against the enum, so pending,
PENDING and Pending all reach the same rows. Any enum value works, including
ones no link on the dashboard points at — /admin/ico/offer/status/failed and
/admin/ico/offer/status/cancelled are both real pages.
The table itself is the same one as /admin/ico/offer, twelve rows to a page,
with create and delete available and edit disabled. Its submittedAt column
shows the wait as a relative time with the exact timestamp on hover, and it lives
in the expanded row rather than the collapsed one.
Clicking a row opens /admin/ico/offer/[id], which is where every moderation
action lives.
The list pages are gated on access.ico.offer and their rows on
view.ico.offer. The detail page behind them is gated on edit.ico.offer.
A read-only reviewer granted only the access and view keys can therefore browse the queue and open nothing in it. If you want somebody to triage without acting, there is no permission combination that produces it — the page that shows an offering is the page that approves it.
Everything you can do from there — approve, reject, flag, pause, feature, edit, add and remove phases, delete, and the Super Admin emergency cancel — is in Reviewing offerings.
The activity feed
The right-hand card is a page of ico_admin_activity, the addon's own log. It is
the newest 12 rows, not the table, and the card says which it is looking at:
the description names the cap and the whole-table total, so "the last 12 of 340"
never reads as "340".
The log records more than admin decisions. Every investment, token release
submissions, vesting tranche releases, settings changes, refund runs and
emergency cancellations all land in it, as do the moderation actions themselves,
offering edits, and phase additions and deletions — eleven writers in all, each
row naming the acting user. Offering creation is not one of them. Neither
/admin/ico/offer/create nor the creator's launch form writes a row, so an
offering's history here starts at the first decision taken on it rather than at
its arrival; the absence of a creation entry is not evidence that an offering
appeared by some irregular route.
Nine of the eleven writers also store a JSON details blob. The two that file a
row under a bare action verb — offering moderation and the transaction desk —
leave details NULL. The log is separate from the platform's own audit trail,
and this card is the only screen that shows it: the ICO admin ships no activity
page of its own, so anything older than the newest 12 rows has to be read from
ico_admin_activity directly.
The six tabs filter on the row's type. The moderation endpoint stores that type
as the bare action verb — approve, reject, flag, unflag, pause,
resume — while the tabs look for approval and rejection.
The consequence is specific and misleading: the Approvals and Rejections
tabs come back empty on an install with thousands of both, and because the
whole-table count for those two labels is also zero, the empty state states
outright that no activity of that kind has ever been recorded. Flags,
Pauses and Resumes do match and are correct. unflag rows, and every
non-moderation type, appear only under All.
Until this is corrected, read the feed on All, or query ico_admin_activity
directly when you need a complete approval history.
Two more quirks worth knowing before you use this log as evidence:
- Settings changes are filed against a placeholder offering id of all zeroes, so they never appear under any real offering. Query the table for those.
- The transaction desk writes its actions here too, under the type
verify,reject,save-noteorremove-note, against the offering the contribution belongs to. Arejectrow is therefore ambiguous between a refused offering and a refused delivery, and nothing on the row resolves it: both writers store only the type, the offering, its name and the admin, with nodetailsand no contribution id. Tworejectrows are told apart only by which offering they name. For anything finer, read the offering's status and its contributions' statuses against the row's timestamp.
Where else the same queue appears
The platform admin dashboard at /admin carries an operations inbox, and pending
ICO offerings are one of its queues — labelled Token Offerings, gated on
access.ico.offer, aged against the same 72-hour approval budget, and linking to
/admin/ico/offer.
The inbox only counts queues for enabled extensions, so disabling ICO removes
that row entirely rather than showing it at zero. The admin ICO area itself sits
behind a licence gate for the ico extension: the nav and footer stay, and the
content is withheld. A queue that vanishes from the inbox without being emptied
is a disabled extension, not a cleared backlog.
Working the console
-
Read the review band first. Overdue is red and it is the only figure on the page an operator is meant to trust without reloading. Anything in it is a creator who cannot launch and has nobody to ask.
-
Open the pending queue from Review now or the Awaiting Review tile. Work the oldest first — the meter is ordered by wait, and the detail page is where the decision is made.
-
Check Live Offerings against the status distribution. An
ACTIVEcount that never falls while offerings pass their end dates means the hourly cron is not settling anything. Nothing reachesSUCCESSorFAILEDwithout it; see Install. -
Read Total Raised as two numbers, not one. The headline includes pending and refunded money; the caption's released figure is what has actually reached creators. A large gap between them is contributions sitting undelivered, which is the transaction desk's problem rather than this page's.
-
Skim the feed on All. It is twelve rows, and it is the fastest way to see whether another administrator has already acted on the thing you are about to act on.
Related
Every action behind the pending queue — approve, reject, flag, pause, phases, and the Super Admin emergency cancel.
Where Total Raised drills through to, leg by leg: verifying a delivery, rejecting one, and every path money takes out of escrow.
What the creator sees once you approve, and why you cannot act through their console.
All fifteen permission keys, every route, the settings keys and the two cron jobs.