Bicrypto 6.7.3
27 August 2026
This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.
Core v6.7.3
Release Date: August 27, 2026 Tags: SECURITY, KYC, COMPLIANCE, MARKET NEWS, EXCHANGE, PUBLIC API, DEPOSITS, WALLETS, RPC, PERMISSIONS, TRANSLATIONS
Overview
A market-news, order-engine and deposit-safety release — and the release that finishes the KYC document move 6.7.2 began.
If you have applied 6.7.2 and have not run the KYC migration, your existing identity documents are unreadable right now — not by the customers who uploaded them and not by your own reviewers. That migration is the one step this update does not perform for you, and it applies whether you are coming from 6.7.1 or 6.7.2.
Two further things want a decision before you upgrade rather than after. Every announcement still marked active will start appearing to every signed-in customer the moment they load a page, because until now nothing displayed them at all. And ecosystem wallets stop handing out deposit addresses on chains this install cannot credit — deposits that were quietly never arriving. Alongside that: market news becomes a subsystem you own rather than one constant in the source, spot limit orders gain time in force, and seven unauthenticated market-data endpoints go live on upgrade. Read Upgrade Notes first.
Update Instructions
pnpm updatorThere is a schema change this time. This release adds the
market_news_provider table and a new permissions seeder. Neither has to be run
by hand: pnpm updator runs the migration step and then the seed, so the table
is created the first time the backend starts with schema sync in its default
mode, and the Market News permission grants are written by the seed. If you run
with DB_SYNC=none, see Upgrade Notes — that is the one install shape that
does not get the table.
Restart the backend and ship the frontend build. The rebuild is what generates
the per-page translation files, so a copy-over update must replace
frontend/public/i18n too. The one manual step is the KYC document
migration in Upgrade Notes, and it applies whether you are coming from
6.7.1 or 6.7.2.
Upgrade Notes
Run the KYC document migration
6.7.2 closed the old /uploads/kyc/ path permanently, at the backend and at the
frontend. Documents already on disk kept their old location, so from the moment
that release was deployed they return 404 to everyone — the applicant who
uploaded them and the operator reviewing them alike — and the document fails to
load with nothing on screen explaining why.
This migration moves them into the private store and rewrites the stored paths.
It reports first and changes nothing without --apply:
pnpm db:migrate:6.7.3
pnpm db:migrate:6.7.3:apply- Safe to re-run. It copies and verifies by size before it removes anything, and
sweeps unreferenced files to
_orphaned/rather than deleting them. - Set
KYC_DOCUMENT_DIRin.envbefore applying if the documents belong on a separately backed-up volume — otherwise they move somewhere the server does not look. - Add that directory to your backups. Losing it means asking every verified customer to submit their documents again.
Review your active announcements before you upgrade
Publishing an announcement did nothing visible before this release. Every notice was being pushed to every signed-in browser and discarded on arrival, so there has never been any pressure to switch an old one off — the admin screen even ships a tile counting announcements that are live and more than ninety days old. Announcements have no expiry: every row whose status is Active is shown to every signed-in visitor, however old it is, one card at a time, each needing its own dismissal in each browser.
- Open Admin → System Announcements and read every row still marked Active.
- Deactivate anything stale, superseded or written for an audience that has moved on. Do this before you upgrade, not after — after is when your customers have already read it.
Deposit addresses on chains this install cannot credit
Ecosystem wallets used to be issued a deposit address on chains this deployment
has no way to watch, and coins sent to one of those addresses were never
credited. Eight chains — Bitcoin, Litecoin, Dogecoin, Dash, Solana, Tron, TON
and Monero — were exempt from the network check entirely, and Solana is the one
that cost money: the Solana service recognises only the literal values mainnet
and testnet, so an unset SOL_NETWORK, or the common mainnet-beta, points it
at devnet while the address stamped on the customer's wallet comes from
SOLANA_NETWORK and defaults to mainnet. The shipped example environment file
sets no {CHAIN}_NETWORK key at all.
- Set
SOL_NETWORK="mainnet"before upgrading if you list Solana, and check the other seven against the network recorded on your token rows. - The shipped configuration also carries
CELO_NETWORK="alfajores"while the chain registry defines only mainnet and sepolia for Celo, so a stock install has been issuing Celo addresses it could never credit. Correct or remove it. - Expect new ECO wallets to carry addresses on fewer chains. Every chain that disappears is one whose deposits could never have been credited. Addresses already written into existing wallets are left in the database untouched, and admin custody screens still show the full map.
Two RPC keys that were decorative now steer real traffic
SOL_MAINNET_RPC and XMR_MAINNET_RPC (and their network siblings) were
accepted and graded by the admin chain-requirements screen and then never
dialled. Both are now used. Check both before you upgrade.
SOL_MAINNET_RPCreplaces the public cluster rather than sitting in front of it, so a stale, wrong or expired URL there will start taking every Solana read. Correct it or clear it.XMR_MAINNET_RPCnow takes precedence overXMR_DAEMON_RPC_URL, the key Monero installs actually use. Anyone who typed a value there to satisfy the requirements badge will find monerod calls — including the fee estimate every withdrawal depends on — going to that value instead of their own daemon. Clear it, or set it to the same daemon URL.- A value that is not
http://orhttps://is ignored by design on both keys and the chain falls back to its previous default.
Seven public endpoints go live the moment you upgrade
The new market-data endpoints are unauthenticated and no setting gates them. GET requests are not rate limited on this platform — the limiter covers only POST, PUT, PATCH and DELETE — so they are open and unthrottled by design.
- If you do not intend to be listed on an aggregator, block
/api/public/summary,/api/public/assets,/api/public/tickers,/api/public/ticker,/api/public/pairs,/api/public/orderbook/*and/api/public/trades/*at your proxy. Deny the seven individually rather than blocking/api/public, because the referral-link lookup lives under the same prefix. Use the recipe in Apache or Nginx rather than writing your own: a rule placed in.htaccess, or a vhostRewriteRuleanchored^api/public/..., loads without error and denies nothing. - If you do, put a CDN or proxy cache in front of those paths. The depth and tape endpoints for your own-engine markets are read straight from the matching engine's store with no snapshot in front of them, one depth request can walk up to five thousand book rows per side, and the five-minute provider volume total is a full scan of the exchange order table, which carries no index on the symbol or on the created date. With Redis unavailable none of the snapshots exist and every crawler poll pays the full cost.
Replace frontend/public/i18n on a copy-over update
Production builds no longer ship the whole translation catalogue in every page;
each page's remaining strings are generated into frontend/public/i18n at build
time. That directory is not in version control, and a chunk set left over from
the previous release is accepted as valid rather than rejected — so an install
updated by copying files over an existing one, without replacing that folder,
would keep serving last release's wording, and strings added in this release
would show as plain English.
pnpm build:frontend, the installer and the updater all regenerate these files already. Only a barenext buildrun by hand, or a partial hand-copied deploy, misses them.- If you update by copying files over an install, replace
frontend/public/i18nalong with everything else.
Market News permissions are granted to whoever already posts announcements
Admin → Market News had no sidebar entry and no role but Super Admin could use it. The five Market News permission keys existed and were tickable in Admin → Roles from the start, but no seeder had ever assigned them — and that is not peculiar to Market News, because no seeder on this platform grants any role anything. The new seeder hands the five keys to every role that already holds the System Announcements key, and the new News Providers console deliberately shares them.
- A role you gave System Announcements will now also be able to open, write and delete Market News stories and change where the trading terminal's news comes from. Decide whether that is what you want before you upgrade.
- On an install where no role holds System Announcements — including every fresh
one — this grants nothing, and Market News stays Super-Admin-only until you
tick the keys in Admin → Roles or run
pnpm sync:permissions -- --grant "<Role>" --applyfrom the backend directory. - The grant re-applies on every
pnpm seed, and the standard update runspnpm seed. Removing the keys in Admin → Roles does not stick — they come back at the next upgrade. If a role should never curate the news feed, take System Announcements off it instead.
The new table and the seeder run themselves, with one exception
market_news_provider holds one row per news source. Fresh installs get it from
the shipped schema; an upgrading install gets it the first time the backend
starts with schema sync in its default mode, which pnpm updator performs. There
is nothing to run by hand.
- An install running with
DB_SYNC=nonenever gets the table. On that install the fifteen-minute news sync stops pulling stories altogether — it logs a warning and returns zero rather than failing loudly — and Admin → News Providers answers with an error instead of the provider list. Create the table or allow one sync-enabled start. - A vendor key saved on a provider row is stored in the clear and therefore appears in any mysqldump or admin database backup. The environment variable is still honoured and takes second place, so an operator who would rather keep the key out of the database simply leaves the field empty.
Added
A News Providers console, and three more sources
Market news is no longer tied to one source chosen in code. A new console under Admin → News Providers runs any combination of four sources — Finnhub, CryptoCompare, CryptoPanic and your own RSS or Atom feeds, up to twenty of them — and each card shows whether it is switched on, what happened on its last run, and how many stories it is holding.
- Added CryptoCompare, CryptoPanic and a generic RSS/Atom reader alongside
Finnhub. Each arrives switched off. CryptoCompare and CryptoPanic need a key,
either pasted into the screen or set as
APP_CRYPTOCOMPARE_API_KEYandAPP_CRYPTOPANIC_API_KEY; the RSS reader needs no account at all, which makes it the one source that works on an install with no credentials configured. - Added per-provider settings in place of three values that were compiled into the platform: how many stories to pull per run (one to five hundred, default sixty), how many days to keep them (one to three thousand six hundred and fifty, default thirty), and — on Finnhub, CryptoCompare and CryptoPanic — which categories to ask for. RSS takes a category per feed URL instead. Retention is per source, so one provider can keep a year while another rolls off after a week, and it keeps applying to a source you have switched off. Lowering it deletes nothing immediately; the next sync prunes to the new age.
- Added a vendor key field on each source that needs one. A key saved here overrides the environment variable and applies from the next sync with no restart; the screen says which of the two is in use, and emptying the field falls back to the environment. No endpoint ever returns the value — the console reports only whether one is stored.
- Added Test, which asks the source for stories using the values currently in the boxes, including a credential or feed URL you have not saved yet, and reports what came back without storing a story or changing a setting; and Sync now, which runs the same job the scheduler runs every fifteen minutes and does store what it finds.
- Stories your own desk wrote by hand are never touched by anything on this screen, and are never pruned.
A table behind the news providers
- Added
market_news_provider, one row per source: its switch, the vendor credential, the categories it is asked for, how many stories per category per run, how many days they are kept, adapter-specific settings such as an RSS feed list, and the outcome, count and message from its last sync. Any number of rows may be enabled at once — unlike the FX provider table's single-active rule, news is additive. - De-duplication is scoped to each source, so a story carried by two enabled providers is stored once for each and appears twice in the terminal.
- See Upgrade Notes for how the table arrives and for the
DB_SYNC=nonecase.
Time in force on spot limit orders
A limit order routed to the connected exchange provider can now carry immediate-or-cancel, fill-or-kill or post-only, and the instruction actually reaches the venue. Until this release the order route called the exchange with five positional arguments and no parameters object, so such an instruction could not travel however it was sent and the order simply rested as good-till-cancelled.
- Added the three instructions on the provider-routed exchange, with what each venue can honour enforced rather than quietly degraded. Binance and KuCoin accept all three. XT accepts immediate-or-cancel only — its own spot symbol payload lists good-till-cancelled and immediate-or-cancel and nothing else, and there is no code path that could turn a post-only request into something XT spot understands. A provider the table does not know gets good-till-cancelled and nothing else, because defaulting an unknown venue to whatever the last one happened to do is the direction that loses money.
- Added the four choices to the Pro terminal's spot limit order form, which asks the backend which of them the connected venue can honour rather than assuming: an XT install sees two buttons where a Binance or KuCoin one sees four. Where the answer is good-till-cancelled alone, where no provider is configured, or where the request fails, the row is hidden entirely rather than offered as a single button that cannot be turned off.
- Added
GET /api/exchange/order/capabilities, open to any signed-in user, reporting which of the four instructions the connected provider can be asked for. The answer is a property of the install rather than of a market — one provider is active at a time — and covers limit orders only, because a market order never rests. It does not disclose which provider the operator routes through. The order form calls it once per page session on spot markets. - A value the venue cannot express is refused with a 422 before any balance is held, so there is nothing to undo. An order that claims to be fill-or-kill and then rests on the book is a promise broken with somebody's money on it.
- A token the platform has no such thing as is refused with a 422 rather than defaulted to good-till-cancelled. Case and surrounding whitespace are not that: a lower-case value, or one with a stray space either side, is read as what it plainly says and honoured.
- Market orders carry no time in force at all — post-only is refused as a contradiction, and fill-or-kill because honouring it means proving the whole fill before committing and nothing on this path can do that. Futures orders offer no choice, because the futures route does not read the field, and conditional (stop) orders offer none on any market.
- Sending no field still means good-till-cancelled, so every existing client — the mobile app, the copy-trading mirror, the terminal's market tab — is unaffected, and the request the platform sends for a plain order is byte-identical to the one it sent before.
Public market-data endpoints for aggregators
Seven new unauthenticated endpoints publish this exchange's market data in the
shapes CoinMarketCap and CoinGecko crawl: /api/public/summary,
/api/public/assets, /api/public/tickers and /api/public/ticker (both
spellings served from one snapshot, because CoinMarketCap's own spec names the
singular and a crawler builds the path itself — a venue that answers only one of
them fails the integration on a spelling), /api/public/pairs,
/api/public/orderbook/{pair} and /api/public/trades/{pair}.
- Added all seven, unioning both venues the platform runs — the markets routed to the configured exchange provider and the markets matched by the platform's own engine — because an adapter reading only one under-reports the exchange by the whole of the other. A pair may be spelt with an underscore or a hyphen, in upper or lower case; a slash works only percent-encoded.
- They never answer 404 on a cold cache, because an aggregator reads a 404 as a dead exchange; an unknown answer is an empty document. The two per-pair paths are the exception: an unlisted pair is a 404, and so is a trade tape the venue does not own.
- They never publish a number the venue cannot stand behind. A market with no live price is omitted rather than quoted at zero; CoinGecko's pair list deliberately inverts this and lists every market, priced or not, because omitting one there tells the crawler the pair does not exist. Twenty-four-hour volume is what traded here — the provider venue's total is summed from this platform's own filled orders and an own-engine market's from its own trade tape, never the upstream exchange's volume republished under this venue's name, which is the single thing a listing review exists to catch.
- The published book for own-engine markets carries order-backed depth only. The AI market maker's display levels have no resting order behind them and cannot be filled against, so they are excluded from the book, the tape and the volume totals, and this book can legitimately be thinner than the one on the trade screen. A book that could not be read answers 503 rather than an empty book, because an empty book is a positive claim that nobody will trade this market.
- The taker's side is not published on a trade, because the stored tape records both legs of every fill with a fixed buy/sell pair regardless of which side crossed, so any value would be a coin flip presented as a fact.
- Two limits worth knowing before you point an aggregator at this. The asset document publishes no per-asset withdrawal minimum or maximum — the fields exist in its shape but nothing fills them in, so every asset is published with its name and its deposit and withdraw flags alone. And an own-engine market whose tape holds five thousand prints inside the last 24 hours publishes no twenty-four-hour volume at all rather than a truncated one, with the market maker's prints counting toward that five thousand even though they are excluded from the total — so a busy maker-quoted market can appear on an aggregator with no volume figure.
- On an install without the Exchange Engine only the provider venue is listed, the other documents are shorter, and every trade-tape request answers 404.
- See Upgrade Notes: these are live on upgrade and nothing gates them.
RPC failover for the platform's own Solana, TON, TRON and Monero clients
Failover across several endpoints existed only for the EVM chains, inside the Exchange Engine. The platform's own Solana, TON, TRON and Monero clients each dialled exactly one endpoint — Solana's hard-wired to the public cluster, the other three configurable but holding a single URL — so an operator whose node was rate-limited had no second endpoint to name.
- Added a shared failover loop for read calls on all four: list several
endpoints, separated by commas, in the chain's RPC key, or use the matching
{CHAIN}_{NETWORK}_RPC_FALLBACKkey. A read that fails on one moves to the next, the endpoint that just failed is never retried within the same call, and every configured endpoint gets one attempt. Each failover is logged with the URL redacted, because the path segment of a provider URL is itself the credential. - Monero is the narrow case. Only the two calls the platform makes to monerod itself — the start-up health check and the fee estimate on the withdrawal path — fail over. Every balance read, deposit scan, wallet refresh and transfer goes through monero-wallet-rpc, which is still a single URL.
- Broadcasts are deliberately excluded on all four chains: a send that appears to fail may already have reached the network, and re-sending it elsewhere is how one withdrawal becomes two.
- Installs with one endpoint behave exactly as before — one attempt, and the original error passed through untouched, so existing error handling still recognises it. When every endpoint fails the call raises an explicit "all endpoints failed" error.
- See Upgrade Notes for the two environment keys this starts using.
An account dashboard at /user
Signed-in customers now have an account dashboard at /user — a page that had
never existed, which is why the installed app's own Dashboard shortcut opened the
not-found page.
- Added the page: four tiles (total balance, pending deposits or wallets in use, security score and two-factor state), the five most recent account activity entries, a verification card when KYC is switched on, and quick links to the wallet, deposit, withdraw, transfer, history, notification, profile, API-key and support screens.
- Fixed the updater's removal list, which still carried this page's path from an earlier release. Left there, every updated install would have had the new page deleted immediately after updating and re-opened the same 404 that the installed app's Dashboard shortcut points at, while a fresh install of the same release worked.
- Nothing in the site menu points to it yet: it is reached by URL or from the installed app's shortcut.
An x-pathname request header
- Added an
x-pathnameheader carrying the current path, stamped by the request pipeline onto every request that renders a page, so the layout can start downloading the right translation files earlier. Nothing else reads it — no routing, authorisation or access decision — and the pipeline overwrites any value a client sends on every path it handles. Worth knowing if you run a reverse proxy with a strict header allow-list or header logging.
Changed
The KYC document migration is a command, not a paragraph
It shipped in 6.7.2 as a script path quoted in a release note and referenced nowhere else — no package script, no upgrade chain, no installer step. Every other release data migration on this platform is a command you can run.
- Changed to
pnpm db:migrate:6.7.3andpnpm db:migrate:6.7.3:apply, dry-run by default like the rest of them.
Market News is granted to the roles that already post announcements
Admin → Market News was missing from the sidebar and unusable by every role but Super Admin, and the lockout was quiet rather than loud: typing the address opened the screen to a table that stayed empty forever, because the browser never asked the server for a row and so nothing was ever refused out loud.
- Changed the seeders to hand the five Market News permissions — open, read, create, edit and delete — to every role that already holds the System Announcements screen key. Nothing had ever granted them.
- Added
/admin/system/news/providerto the admin route-permission map, requiringaccess.market.news, the same permission that already guards/admin/system/news; and a matching sidebar entry, News Providers, under the market section with the same permission. No existing route's permission changed and nothing was removed. - What this does to a hand-curated install is a decision, not a detail. See Upgrade Notes.
The news feed is switched on in the admin screen, not by an environment variable
Before this release the feed had exactly one behaviour: if APP_FINNHUB_API_KEY
existed the platform pulled up to sixty crypto stories every fifteen minutes and
kept them for thirty days, and if it did not, nothing happened. Those three
numbers lived in the code and there was no way to change any of them.
- Changed the switch from the environment to the provider row. The first time
the new screen or the scheduled job runs, the Finnhub entry is created already
enabled and set to crypto, sixty stories, thirty days — exactly what the
constants did — provided that key is present at that moment. That first moment
is the only time the environment decides anything: adding a vendor key to
.envafterwards will not start a feed, and the other three sources are never switched on by an environment variable at all. - Changed when aged stories are pruned. Pruning now runs on every scheduled run, for every source listed on the screen including a switched-off one, where before it happened only on a run that actually fetched something. An install whose Finnhub key had been removed kept its old provider stories indefinitely; after upgrading, those past thirty days are deleted. The terminal never showed anything older than thirty days, so no customer sees a difference.
- Stories already collected are not re-imported and not rewritten — the de-duplication key each Finnhub story already carries is the one the new writer generates.
Story links, timestamps and vendor failures are handled more strictly on the way in
- Changed every market-news source to go through one HTTP door with a fifteen-second timeout and a ceiling on how much of a response is buffered — eight megabytes for the vendor APIs, four for RSS feeds — so an oversized feed can no longer exhaust memory. The old single-provider client already timed out at fifteen seconds, so nothing about slow vendors changes; the size ceiling is what is new.
- Changed how story links are stored: a link is kept only if it is an ordinary http or https address that fits the column, and one too long to store is dropped rather than sliced. A story may therefore arrive with no link where it previously carried one that resolved nowhere. Nothing rewrites links already in the table.
- Changed timestamp handling — dates are read by magnitude and a story dated more than a week ahead is refused. This exists for the new RSS and aggregator sources, whose date fields are not all publication times; against the Finnhub feed carried over from the last release every date reads exactly as before.
- Fixed a vendor failure that reported nowhere. A Finnhub key that was rejected or not entitled to a category came back as a normal HTTP 200 with the error inside the body, which the old client read as an empty list — no log line, no sign anywhere in the admin — so a dead key looked like a quiet news day indefinitely. Failures are now classified once, and the provider row separates "your key is wrong or not entitled" from "the vendor is having a bad afternoon".
- An RSS feed that declares its own XML entities is refused unparsed, because that construct is a memory-exhaustion trick and no legitimate feed needs it.
- The API keys these vendors carry in their query strings are deliberately kept out of every message and log line. An RSS feed's own address is named in its failure message, so an operator running several feeds can tell which one broke — avoid putting a token in a feed URL.
The terminal's per-symbol news filter has more to work with
The terminal decides whether a story is about BTC by reading the headline and summary for the ticker or the asset's name, so it misses one that says only "the largest cryptocurrency" and catches one that mentions Bitcoin in passing. That matching is unchanged in this release — what changed is what it has to work with.
- Changed by supply, not by logic. The filter has always preferred a provider's own per-story symbol tags over reading the prose, but Finnhub, the only source available until now, has never sent any, so it always fell through to guessing. Of the four sources now on offer, CryptoPanic ships an explicit instrument list on every post and CryptoCompare carries asset codes.
- This reaches only stories fetched after such a source is enabled — everything already in the feed is still matched on its text — so an install that changes nothing behaves exactly as it did before.
Orders the venue kills the moment they are placed
Adding immediate-or-cancel and fill-or-kill created a fourth outcome the settlement machinery had never had to answer. Until now the only outcomes this route handled were an order that filled outright and an order that rested on the book, so "not filled" and "still resting" were the same branch, and the create path always reserved funds for whatever did not fill.
- Changed so an order that comes back cancelled, expired or rejected at placement reserves nothing and is written terminal straight away. A hold placed against a cancelled row would never be released by anything: the once-a-minute reconciliation sweep and the order websocket both look at open rows only, so nothing would ever look at that row again and the money would be stranded for good. Any slice that did fill before the kill is still settled in full, in the same transaction, exactly as before.
- Changed the three settlement writers — cancel, the websocket watcher and the reconciliation sweep — to recognise three settlement models where they recognised two: funds held, funds debited outright (older rows), and nothing reserved at all. An order in the third state gets nothing moved for it.
- Without that third answer an unfillable fill-or-kill would have come back expired with its whole size counted as unfilled, and cancelling it would have credited the full order cost to a wallet nothing was ever taken from — money created on demand, once per order, by anyone willing to send an order nobody will fill. That path never shipped; it is closed in the release that opened it.
- The flag recording which model an order is on is also no longer combined with a guess based on whatever happens to be held in the same wallet, so an order that explicitly reserved nothing can no longer release a different resting order's hold.
Every page carries a fraction of the translation catalogue
Every page this platform served used to carry the entire catalogue inside the document — all sixty-two namespaces, 22,973 strings, about 1.45 MB — for every visitor, on every hard navigation, whatever the page was. On the sign-in page that was roughly 450 KB gzipped, the overwhelming bulk of the document, to render a form with about a dozen labels on it. The homepage paid the same price, and so did a 404.
- Changed production builds to put only a core set in the document — the strings the header, sidebar and error screens render, plus the labels a quarter or more of pages share: 566 strings, about 25 KB, a 98.3 per cent cut. Each page's remaining strings arrive as a separate file, and the layout hints those files to the browser while the HTML is still being parsed, so on a non-English install the navigation labels settle sooner after a hard navigation.
- Changed how those files are cached: fetched with a version stamp they are now cached by the browser for a year and never revalidated, where previously the browser checked every one of them with the server on every page load. Each release mints a new stamp, so a new release cannot be shadowed by a file cached under the old one. Without the stamp they revalidate exactly as before.
- Development is deliberately unchanged and still loads the whole catalogue, so a translator can edit a file and see it immediately.
- If the generated files are missing or incomplete they are rejected outright and the full catalogue is served exactly as before, so a build that skips the generation step is heavier, never wrong. That safety net is also what makes a stale copied-over folder dangerous rather than obvious — see Upgrade Notes.
What a missing translation looks like
- Changed the fallback for a string with no translation. It used to be painted
on screen exactly as written in the code — customers saw things like
pay_with_stripeon a deposit button. It now renders as readable words (Open menurather thanopen_menu) while the app fetches the real string in the background, so the readable version is a flicker rather than the final answer. Now that a page no longer ships every string up front, a brief gap is ordinary rather than exotic. - The trade-off is that a genuinely untranslated string is harder to spot — it looks like ordinary English rather than an obvious defect — so a translator reviewing a locale should compare against the catalogue rather than trust the screen.
Duplicate translation strings folded together
- Changed the catalogue: one hundred and seventy duplicated strings were removed and one hundred and fifteen shared replacements added, leaving 22,973 in total. Words that existed separately in five or six places — swap, wrap, unwrap, gas, confirm transaction, martingale, the order-book display modes, the relative-time labels such as "3 minutes ago" — now live in one shared place each. No source file is left asking for a removed string, and the number of translated strings per language is unchanged.
- Changed three admin sidebar entries that had hard-coded English labels — Reported Content, Investment Compliance and Licence Attestations — into translatable ones. They still read English in every language until someone translates them.
Translation files shipped in the release archive
- Changed the generation step to clear its output folder before writing instead of adding to whatever was already there. Because those filenames are derived from page paths, renaming or deleting a page used to orphan its file rather than replace it, and nothing ever removed the orphans — they accumulated inside the publicly served folder and were copied into every release archive.
- Changed each generated file to carry the number of strings it wrote, with the platform refusing any file that cannot show at least that many, so a half-written file left by an interrupted build is discarded rather than used with strings missing.
The homepage and seven extension landing pages arrive complete
Every one of these pages used to hand the browser its own loading pass and fill itself in a beat after paint. On the payment gateway and forex landing pages the hero shipped with no figure row at all and then grew one, pushing the rest of the page down; the store's deals, trending items, best sellers, categories and reviews, the token-sale project cards, the staking and affiliate metric strips and the copy-trading leaderboard were all grey placeholders in the HTML a visitor — or a search-engine crawler — actually received; and the homepage's sixteen product sections carried built-in placeholder headlines and no live numbers until the request answered.
- Changed the homepage and the landing pages for affiliate, copy trading, e-commerce, forex, the payment gateway, token sales and staking to resolve that data during the server render, so the first HTML is the finished page and nothing rearranges itself under the reader.
- The sections themselves are unchanged — same products, same order, same figures. Rendering the homepage now costs one extra, uncached call for those figures, so its first byte can take marginally longer on a busy install.
- A page whose server fetch fails still fills itself in from the browser exactly as it did before, so a backend hiccup degrades a marketing page rather than breaking it. The NFT and peer-to-peer landing pages are not part of this batch.
The animation engine downloads after the page instead of inside it
framer-motion's feature set — gestures, drag, layout projection, the animation drivers — used to be compiled into the bundle every route already downloads.
- Changed three hundred and sixty-one files across the frontend — the blog, landing and marketing sections, the customer area, the admin console and the shared components — onto the lightweight animated-element namespace, and moved the feature set into one file fetched after the page hydrates. The feature set is identical to before and the operating system's reduce-motion setting is honoured exactly as it was.
- What changes is timing: on a slow connection an animated section can appear a fraction later than it used to, and hover, tap and layout effects are inert for that same fraction, because both now wait for that one extra file.
- One symptom worth recognising if a deploy is ever served incomplete: animated elements that cannot get that file stay at their starting state, which for one hundred and forty of the one hundred and seventy animated elements on the marketing pages is fully transparent — they would simply never appear, with nothing in the browser console naming the cause. The product retries the fetch once, which covers a dropped request but not a file genuinely missing after a hand-copied deploy. Because that file is loaded once for the whole site, a double failure affects every animated page.
- Chart screens are unaffected either way, because the charting package ships its own prebuilt copy carrying the full feature set. Nothing extra is needed on upgrade — the new file is produced by the frontend rebuild every release already requires.
The admin assistant is no longer in the bundle every page blocks on
The admin assistant is a large component that cannot draw anything unless the visitor is signed in, standing on an admin screen and running the AI Support add-on — yet it was compiled into the main script every visitor downloaded and parsed before a page could become interactive, including logged-out readers on the blog, the landing page and the login form.
- Changed it into a file of its own, fetched separately once the page is already interactive. It is still requested on every page load rather than only for administrators; what changed is that it is off the critical path.
- The one visible difference is on the admin side: an administrator who keeps the assistant rail pinned now sees it appear a moment later after a hard reload, and if that separate file fails to arrive the rail stays absent until the page is reloaded.
Database backup and restore refuse a garbled DB_PORT
- Changed both operations to check
DB_PORTbefore connecting. Anything that is not a plain whole number between 1 and 65535 —3307abc,0, a hex form such as0x0CEB, a decimal such as3307.0— now stops the operation with a message naming the bad value and the range a port may take. Previously a value that could not be read as a number at all, and0, were treated as 3306, while a hex or decimal form was accepted as whatever number it evaluated to. - To be clear about what this is: no backup or restore ever reached the wrong
database. The platform reads
DB_PORTthe same way and lands on the same server, so a dump taken under a mistyped value was a dump of the database the platform was serving from, and a restore dropped and replayed that same database. Dumps already in the backup folder do not need re-checking. This is a refusal added ahead of two destructive operations, not a correction of a wrong result. - If
DB_PORTin.envis anything other than a plain whole number, correct it before your next backup or restore — including forms that previously worked. Leaving it unset or empty still means 3306, so a stock install is unaffected.
Seven shared admin labels moved to the common translation catalogue
- Changed seven labels shared by several admin screens — Opacity, Appearance, Alignment, Path, Exchange, User Wallets and "Accepted by" — out of the admin-only catalogue into the shared one, repointing the page builder's Design tab, the database backup table, the geo-restriction log, the binary market creation screen, the user detail page's wallet card and the investment compliance screen. In eighty-two of the ninety languages the wording is exactly what it was.
- Fixed, as a side effect, the investment compliance screen's "Accepted by" line in eight languages — Amharic, Arabic, Assamese, Bengali, Bulgarian, Greek, Persian and Fijian — where it had been stuck in English while the shared catalogue already carried a real translation.
- Four "None" labels on the same screens were pointed at a shared entry carrying the identical word in all ninety languages, so those read exactly as before.
Fixed
System announcements now reach the people they were written for
An operator published an announcement under Admin → System Announcements and no signed-in customer ever saw it — no banner, no bell entry, no page — with nothing anywhere reporting a failure. Every notice was already being pushed to every signed-in browser and thrown away on arrival, because the only message handler in the web app looked for notifications and discarded everything else.
- Fixed by giving announcements a listener. Signed-in visitors — customers and administrators alike, on every page including the full-height trading terminals — now get a dismissible card pinned to the bottom start corner of the window (bottom-left in a left-to-right language, bottom-right in a right-to-left one) carrying the same General / Event / Update label and colour the operator saw on the row they published from. One shows at a time, newest first, and closing one reveals the next.
- It rides the connection the app already holds, so a notice published now appears without a reload, an edit rewrites it in place, and switching one off or deleting it takes it away. Signed-out pages show nothing.
- The card floats above the page rather than sitting in the layout, so it shifts nothing, but it does cover that corner until it is closed, and in a right-to-left locale a toast can briefly overlap it.
- Announcement text is stripped of anything that could run script, so an author cannot put code into every signed-in session. The card's formatting allowance is narrower than the preview on the admin row: paragraphs, line breaks, bold, italics and links survive, while headings, bulleted and numbered lists, underline, quotes and tables read as flat text and images are dropped altogether. Compose an announcement as plain paragraphs if you want it to look to customers the way it looks in the editor.
- The optional link becomes a clickable "Learn More" only when it is an ordinary http, https, mailto or same-site address; anything else is dropped and the card simply carries no link.
- Dismissals are remembered in that browser only, so the same person on a second device, in a private window or after clearing site data is shown the notice again, and it is never hidden from someone who has not seen it. Two hundred dismissals are kept per browser, after which the oldest is forgotten and a still-active notice can come back.
- Announcements have no expiry. Read Upgrade Notes before you upgrade — every Active row starts appearing to everyone, however old it is.
Deposit addresses were handed out for chains this deployment cannot credit
A customer's wallet page listed every chain in their address map as a deposit network, each with a real address and a scan-to-deposit panel, under a heading inviting them to send funds to one of the supported networks below — including chains this deployment has no way to watch. Money sent to one of those addresses was never credited, and the only trace was a 503 saying deposits cannot be monitored, written to a log nobody was reading, long after the funds had left.
Wallet creation had a rule of its own, and it was wrong in two directions. On the
EVM chains it checked one fact — that {CHAIN}_NETWORK was set and matched the
network stamped on the token row — while the deposit list and the deposit monitor
both demanded two more: a {CHAIN}_{NETWORK}_RPC endpoint, and an entry for that
network in the chain registry the platform builds its providers from. A chain
satisfying only the first produced a genuine, fundable address, because
generating an address needs no endpoint and no monitor anywhere. This was not
theoretical: the shipped configuration carries CELO_NETWORK="alfajores" while
the registry defines only mainnet and sepolia for Celo, so a stock install issued
Celo addresses it could never credit.
- Fixed wallet creation to use the one rule the deposit list and the deposit monitor use. For the nine chains served by a shared endpoint — Ethereum, BNB Chain, Polygon, Arbitrum, Optimism, Base, Fantom, Celo and MO — that means all three facts. For the eight with their own service it means the network name alone, since they have no endpoint key and no registry chain id to match.
- Fixed three customer screens — the wallet list, the single-wallet page and the deposit screen — to withhold the address of a chain this deployment cannot credit and mark that chain unavailable. The chain stays listed rather than vanishing, because a disappeared row is a support ticket and a named one is an answer: it carries a warning triangle in its network chip, the wallet page opens on the first chain that can actually be credited, and selecting an uncreditable one replaces the address panel with a plain statement that deposits are not available on that network. The blanket "send funds to one of the supported networks below" line is dropped whenever any listed network carries the warning.
- Fixed the customer-facing wallet routes to withhold the address string itself for those chains. Admin custody screens are served by different routes and keep the full address map, so an operator can still answer "where did my coins go".
- Changed address issuance to write a warning naming the chain it withheld and which fact was missing, instead of silently issuing one address fewer.
- New ECO wallets will carry addresses on fewer chains, and every chain that disappears is one whose deposits could never have been credited. Addresses already written into existing wallets are left in the database untouched.
Solana, TRON, TON, Monero and the four UTXO chains are now network-checked
Eight chains — Solana, TRON, TON, Monero, Bitcoin, Litecoin, Dogecoin and Dash —
were skipped by the network check every other chain went through, on the wallet
page, at address issuance, on the deposit currency list and on the deposit method
list. They were treated as network-agnostic, which was never true: each reads a
{CHAIN}_NETWORK setting to decide which network its own service runs against,
so a testnet-labelled token row was offered on a mainnet install and the reverse.
Solana is the one that cost money. With SOL_NETWORK unset, or set to anything
other than the literal mainnet or testnet — mainnet-beta included — the
Solana service connects to devnet, while the address stamped onto the customer's
wallet comes from a different setting, SOLANA_NETWORK, which defaults to
mainnet. A customer was offered Solana, handed a real mainnet-looking address,
and the only process that could have credited the deposit was listening to
devnet. That is the shipped configuration, because the example environment file
sets no {CHAIN}_NETWORK key at all.
- Fixed by checking the network label on all eight, resolved exactly the way each chain's own service resolves it — including that an unset value means mainnet on seven of them and devnet on Solana.
- Changed what happens to such a chain: it is withheld rather than offered. It disappears from the deposit currency list and the deposit method list, no new address is issued on it, and an address already issued is hidden on the customer's wallet page rather than shown under an invitation to send funds.
- Only the network half of the old exemption was removed. The endpoint and chain-id checks still do not apply to these eight, because those are facts about EVM providers and a UTXO chain has neither.
- Set
SOL_NETWORK="mainnet"before upgrading, and check the other seven against your token rows — see Upgrade Notes.
Token deposit scanning stopped dead on an endpoint that caps log queries
On any chain whose RPC endpoint caps log queries — BSC's public dataseed is the observed case — the background scanner credited no token deposits at all. It swept its lookback window in fixed one-thousand-block chunks with a single error handler wrapped around the whole loop, so the first chunk an endpoint refused ended the entire sweep, including the newest blocks where a fresh deposit sits. The refusal came back pass after pass, two minutes apart, for as long as the endpoint kept answering the same way. The visible result was not a noisy log: that chain detected no token deposits in the background at all, and the background scanner is the only thing watching once the customer closes the deposit page. An address that is never swept drops out of the watch list seventy-two hours after its last deposit-page visit, so a deposit missed this way stayed uncredited until somebody went looking for it.
- Fixed by negotiating the window with the endpoint. A refusal naming too large a query shrinks the window; one naming too many requests backs off; an unlabelled limit — which is all BSC sends — does both, because the two faults share one sentence, want opposite remedies, and guessing one of them converges on nothing.
- Changed so a range that still cannot be read is skipped rather than aborting the sweep: the rest of the window is read and that range is covered again on the next pass. The window size an endpoint will actually serve is remembered per chain for the life of the process and grows back gradually after clean sweeps, so a passing cap does not pin a chain to a small window.
- Each sweep is capped at forty-eight requests, so shrinking cannot turn one address scan into hundreds of calls against the endpoint that just asked for fewer — what is left of the window is reported as a gap and picked up as the window slides forward.
- Log lines for a refused call now carry the node's own sentence and code instead of the whole request payload.
System Health reported healthy chains as down when RPC failover was configured
A chain's RPC setting may hold several URLs separated by commas — that is how failover is configured, and the wallet code has always read it that way. Both admin health probes handed the whole string to the network as if it were a single address, so a chain answering happily on two endpoints was scored as unreachable while the platform was trading and crediting deposits on it.
What an operator actually saw was the Blockchain RPC line on the dashboard's Platform Health card: with comma-separated URLs on every configured EVM chain that line turned amber reading "RPC configured but connection failed" and took five points off the platform health score while both nodes were serving blocks. If even one chain was still configured with a single working URL the line read connected, so this only showed on installs that had adopted failover across the board.
- Fixed by probing each configured URL separately, with a chain counting as up while any of them answers. The same correction went into the per-chain health endpoint, which covers all seven EVM chains — Ethereum, BSC, Polygon, FTM, Optimism, Arbitrum and Celo — for both the HTTP and the WebSocket entries.
- Changed the per-chain endpoint to report how many of the configured endpoints answered, appending a count such as "[2/3 endpoints]" to its connected message, so a fallback that has quietly died is visible before it is the last one standing. That endpoint is wired to no admin screen, so it matters only to an external monitor calling it directly.
- Changed how an unusable value reads: one with no
http://orws://scheme now counts as not configured rather than as a failed connection, so the dashboard tile reads "No blockchain RPC configured" for it and it no longer deducts from the score. - Because every HTTP endpoint is now tried, a chain where none of them answers spends one timeout per configured endpoint instead of one in total — up to eight seconds each on the per-chain endpoint, five each on the dashboard tile, which still walks up to six chains one after another. WebSocket probes stop at the first endpoint that answers, because each attempt opens a real socket to the node.
- Only the main RPC setting for a chain is read by these probes, so a URL placed in that chain's separate fallback key is still not checked. Installs with a single endpoint per chain behave exactly as before.
Orders refused for a time-in-force the trader could not see
A time-in-force choice is kept on purpose after an order is placed and when the trader moves between markets, so it does not have to be re-picked after every fill. But the control that sets it is only drawn on limit orders on markets that enforce the field — so a Fill-or-kill or Post-only picked on an ecosystem limit order stayed selected, and stayed in the order, when the trader switched to a market order. The market order came back refused, and with the control not drawn on the market tab there was nothing on screen to change: the only way out was to switch back to a limit order, re-open Advanced options and set the choice back to Good-till-cancelled.
- Fixed by clamping the time in force on the way out to what the market and order type can actually honour, falling back to Good-till-cancelled — which is what an order with no time in force already is — so the order is accepted.
- Nothing needs re-checking: the refused attempts were turned away before any funds were held and before any order record was created.
Server boot loop when the schema sync met an index that already existed
The platform runs its web process and its scheduler process side by side, and both run the schema sync on start-up. When an upgrade introduces a new table index the two can race: each looks, neither sees an index the other has not finished writing, and both try to create it. One won and the other died on start-up with "Duplicate key name" — a crash over a database that was already correct, taking either the site or the scheduler down until the supervisor restarted it and the race resolved itself.
- Fixed by treating a duplicate index name as the desired end state rather than a failure: the sync retries and the process starts instead of exiting.
- Changed the message logged when a sync still cannot finish, which now says that a duplicate key name means the schema is fine and to look for a second backend process syncing at the same moment, before repeating the query for finding genuinely duplicated foreign keys.
- This release is itself an upgrade of that shape — it adds a news-provider table with a unique index — so the guard arrives in the release that needs it.
A negative backup retention count deleted every dump in the folder
DB_BACKUP_RETAIN, the setting that says how many dumps to keep, was accepted
without any checking. A negative number counted as a real setting, and the sum
that decides how many old dumps to prune then asked for more files than the folder
held — so every dump was deleted, including the one the run had just written. The
backup still reported success, and its own message cheerfully counted the dumps it
had pruned, over an empty folder.
- Fixed by requiring a plain whole number of one or more; anything else falls back to keeping ten dumps rather than failing the backup.
- This could only bite an install that had typed a negative number in by hand: the
setting is not in
.env.exampleand is documented nowhere, automatic pruning is itself only one release old, and every other value was already safe — unset, zero and unreadable text all fell back to keeping ten.
Dead push tokens from single-device users were never removed
When Firebase reported that a device token no longer exists — the app deleted, the device wiped, or the token rotated — the platform only acted on that report for accounts with two or more registered devices. An account with exactly one device took a different code path, and the rejection it produced was reported in a shape nothing read, so the dead token was never dropped and every later notification was sent to a handset nobody holds. Single-device accounts are the common case, which is why this went unnoticed.
- Fixed by reporting the rejection in the shape the sender reads, so a refused token is removed from the account's saved push subscriptions the first time it is rejected.
- One limit worth knowing, unchanged by this release: a token registered by the native mobile app is also held in the device registry, and nothing removes it from there when Firebase rejects it — pushes to a dead native install keep being attempted until that install signs out or the customer signs out their other devices. The full clean-up therefore applies to browser push subscriptions today.
KYC applications invented documents out of typed text
The Documents tab on an applicant's own KYC application has to work out which of
their answers are uploaded files by reading the stored text, because the fields on
a KYC level are whatever the operator built and carry no type of their own. That
test used to match any answer containing .jpg, .jpeg, .png or .webp
anywhere inside it. A typed address reading "Flat 2, mydocs.png Street", or a note
saying the applicant had emailed a photo to support, was listed on their Documents
tab as a card reading "Document temporarily unavailable" — permanently, with
nothing the applicant or the operator could do to clear it — and on an application
with no real uploads it replaced the honest "no documents found" message.
- Fixed by requiring the answer to be a filename from end to end, so prose is no longer mistaken for a file.
- Changed in the same pass from four image types to all twelve file types the KYC document store accepts, so a stored PDF, Word, Excel, text or CSV file is treated as a document rather than an ordinary text answer. That half is coverage against a storage shape we have no evidence of on real installs, not a repair of something operators were seeing.
- Operator-side KYC review screens were never affected — they decide by field type rather than by reading the text. Nothing needs to be run after upgrading.
Files served from the API could not pass through a proxy
The backend writes every file reply through one writer, and three of the routes
that feed it — a KYC identity document to its applicant or reviewer, a digital
product a shop customer bought, and a P2P dispute attachment — also put
Content-Length on the reply themselves, beside the one the server writes on
its own. Read directly from the backend the duplicate is harmless. Relayed, it
is fatal: Node's HTTP parser refuses the response, so the development proxy
answered 500, and nginx from 1.23 on answers 502 to an upstream reply with two
Content-Length lines. A reviewer opening a passport, or a customer
downloading what they had paid for, got an error page and nothing to explain it.
- Fixed in the response writer, which now drops any
Content-Lengtha route supplies rather than relying on each route to remember. The three routes no longer write one.
Files can be streamed, in ranges, under real statuses
Two things a file route could not do before this release. It could not send
a body without first reading all of it into memory — acceptable for a
receipt, not for the 25 MB screen recordings P2P disputes now take, which a
player fetches several times per page view. And it could not answer with any
status but 200: the platform pins the status line before a route runs, and a
206 Partial Content that leaves as a 200 is a partial body the player reads
as the whole file, so byte ranges were impossible.
- Added a streaming path to the response writer: a route may hand it a readable and a size, and the body goes out chunk by chunk with backpressure, under whatever status the route names.
- Changed the two private file paths — P2P trade attachments and KYC
identity documents — to carry real HTTP statuses. A browser never consults
CORS for an image, a video or a download, so the eager CORS write that pins
the status is skipped for exactly these two path shapes and nothing else.
A missing document is now a
404to the image that asked for it.
Buying into a token offering failed on any install without the Exchange Engine
The purchase path read models.ecosystemToken directly to find the token's
decimal precision. That model ships with the Exchange Engine, and a missing
addon's models are removed from the model manager rather than left empty — so
without that addon the lookup raised a type error, which the route returned as a
server error. Every purchase on every offering failed, and the token offering
addon does not require the Exchange Engine, so this was a configuration we sell.
- Fixed the purchase path, which now falls back to 18 decimals exactly as it already did when the token record was simply absent.
Copy trading and the trading bots could fail the same way
Five further places read an ecosystem market without checking the Exchange Engine was installed. Both addons are sold without it.
- Fixed each one to take the "market not found" path it already defined, rather than failing with a server error. Copy trading's scheduled path was already guarded and is unchanged.
Labels that stayed in English whatever language the customer chose
The crypto wallet's approval and unlock dialogs were reading their labels from a catalogue in which those words had never been translated, while a translated copy of each one sat in the shared catalogue next to it. In eighty-five or more of the ninety shipped languages the Reject, Confirm and Cancel buttons and the Amount, Action, Type, Contract, To, Unlimited and Chain field labels all appeared as the bare English word: a German customer approving a token spend read "Reject" and "Unlimited" inside an otherwise German dialog.
- Fixed those ten labels to show the translation that was already on disk. Five more — Connect, Unlock, Wrap, Unwrap and Confirm transaction — and the Appearance heading in the profile menu gain a translation in eight languages, among them Arabic, Bulgarian, Greek and Persian.
- Fixed the same class of defect on the order book, the price-alerts panel and the spot stop and OCO order forms — Price, Amount, Total, New, Create, Delete, Pause, Resume, Expired, Paused, History, Average price, Target price and the tick-size None — which stayed in English whatever language a customer had chosen. Across the eighty-nine non-English catalogues roughly sixteen trading labels per language now change wording, and none fall back to English. Two labels on the binary martingale settings and position-sizing calculator, three on the profile dashboard and security tabs, and the "enter an amount greater than zero" error on the investment plan form move with them.
- Changed for English-language sites, which see two shifts only: the order book's cumulative-depth heading now reads "total" in lower case, and the OCO form's "Target price" becomes "Target Price".
- One label moved the other way and it is worth naming rather than burying: the shared word for an empty selection lost its translation in all eighty-nine non-English languages, so the Peak and Off-Peak chips on the binary engine's admin time-analysis panel now read "None" where they used to read "Nichts" or "Aucun".
- Behind all of it, eighty-five labels were re-homed to a different catalogue, forty-five duplicate copies were dropped in favour of one that already existed, and forty unused ones were deleted; every screen that used them was repointed in the same change, and no screen is left pointing at a label that moved.
The product was advertising things it does not do
Three places in the product made claims about the platform that were not true, and all three are corrected.
- Fixed the "3-Column Icon Features" section in the page builder's Insert Section list, whose first column read "Orders reach exchanges in under 1ms with co-located servers across NY, LDN, and Tokyo." None of that is true of this platform: there is no colocation, there is no New York, London or Tokyo presence, and no such latency figure is measured or published anywhere in the product. It also described orders travelling out to third-party exchanges, which is the opposite of the in-house matching engine the column's own heading is selling. Newly inserted sections now say that orders match on your own in-house order book with no third-party venue in between. The heading is unchanged.
- The section's text is copied into a page when the section is inserted, so a page you built earlier still carries the old sentence. Review any public page you built from that section and correct the wording there.
- Fixed the Chart Engine panel on the Binary Options settings screen, which advertised "173+" indicators and "45+" drawing tools — understating the catalogue by fifty-two indicators and eighty-seven drawing tools — and claimed "60 FPS" and "WebGL acceleration" twice, once as a headline tile and once in the paragraph beneath it. Nothing in the product renders through WebGL: the chart paints on a plain 2D canvas, and the WebGL code shipped inside the package is reachable from no code at all. It now states two hundred and twenty-five indicators and one hundred and thirty-two drawing tools, and that tile now describes something the engine genuinely does — indicator output cached against the last confirmed candle, so a tick that only moves the live close recalculates nothing.
- Fixed the same two claims in the Extensions console, and Hummingbot's copy there, which listed Hyperliquid among its tuned controllers. The presets that actually ship published are six — four pure-market-making and two cross-exchange — and the two cross-exchange ones hedge each fill on Binance or MEXC through the bot runner's own account there, with the maker leg always quoting the operator's own book. Its stream description was made accurate too: one socket carrying book, trade, ticker, mark-price and funding channels plus signed private order, balance, position and fill streams on per-channel timers.
- Two spots were missed and are worth knowing about: Chart Engine's drawing-tools card still reads "45+ Drawing Tools" beside the new one-hundred-and-thirty-two figure on the same page, and Hummingbot's stored extension description — shown on the Extensions list card and again beneath the heading on its detail page — still names Hyperliquid. This is admin-facing description text; no add-on behaviour changed with any of it.
The staking page described rewards as claimable when they are not
The staking page told customers "Rewards accrue continuously and can be claimed or compounded whenever you want, including mid-term on flexible pools", under a heading reading "Claim on your schedule". A customer who staked expecting to withdraw rewards mid-term was reading a promise the page should not have made.
- Fixed the copy, which now says rewards are written on the schedule the pool sets — daily, weekly, monthly, or once at the end of the term — and states plainly that pools paying at the end of the term, and positions set to auto-compound, have nothing to claim until they mature. The heading now reads "Claim on the pool's schedule".
- This is a copy change and nothing in the staking engine moved with it. If you run staking pools, check the new wording matches your configured pools before your customers do.
Affiliate referral links
- Fixed the short referral address of the form
/ref/<code>, which sent logged-out visitors to a home-page URL carrying a flag the app reads as "this person was refused access" — a full-screen "unauthorized access" panel painted over the whole viewport, at the exact moment the visitor was meant to sign up. That address now goes straight to the registration page with the code attached. - The scope is much narrower than it sounds: nothing in the product ever handed
anyone a
/ref/link. The link an affiliate copies from their dashboard, builds in the Referral Generator, or shares to social, email or a QR code already pointed at the registration page with the code attached, and reached the form normally both before and after this change. Referral attribution was never lost in any case, because the code was kept in the browser session and credited whenever the visitor eventually registered — so do not expect referral sign-up conversion to move.
Table columns holding an icon name asked the server for that name and got a 404
The admin P2P payment methods list keeps an icon name, not a file, in the same column the table draws as a picture. That name was handed to the browser as if it were a file address, so the browser resolved it against the current page address and asked the platform for a page that does not exist — one wasted request per row, every time the list was opened or paged. The failed request quietly swapped in the generic photo placeholder, so every method wore the same grey square and nothing anywhere reported a problem.
- Fixed by drawing names as icons and never requesting them. Anything that looks like a path or a web address is loaded exactly as before.
- This does not put a picture back on every row: the platform's own setup
catalogue writes plain names such as
landmark,smartphoneandwalletthat the built-in icon set does not recognise in that form, and those rows now show a neutral grey symbol in place of the placeholder photo. Payment methods a customer created from a payment rail do carry a recognised name and now show their icon.
The database backup screen had no page heading
- Fixed the system database backup screen, which rendered its title as a second-level heading and so carried no top-level heading at all — anyone navigating with a screen reader, or with an extension that jumps by heading, landed on an unlabelled page. It now matches the roughly one hundred administration screens built on the standard table.
- Two administration screens are unchanged by this release and still render no top-level heading of their own: the support desk console and the payment-gateway design preview.
The 6.7.2 upgrade note described the KYC change backwards
It said identity documents "stay public" and kept "its old public URL" until the migration was run. The reverse is true. The path is refused by the backend static handler and by the frontend rewrite from the moment the release is deployed, so those documents are unreadable rather than exposed, and KYC review of existing applications stops working until the migration moves them.
An operator reading that note would have treated the migration as hardening to schedule at leisure, when it is the step that gives their reviewers access back.
- Fixed the 6.7.2 note in place, so an operator upgrading past it is not told the opposite of what happens.
Removed
Avalanche and Linea removed from the wallet chain registry
Avalanche and Linea were listed as supported chains by the wallet service while
the ecosystem's own chain registry — the one the provider, the deposit monitor and
the balance reader are all built from — carried no entry for either. Nothing on
the platform ever offered them: the token importer's chain list has never included
either, and the two variables their address issuance depended on, AVAX_NETWORK
and LINEA_NETWORK, appear in no example configuration and on no admin screen.
Reaching either took an operator sending a chain name the interface does not offer
straight to the API and setting an undocumented variable by hand.
- Removed both symbols from the wallet service.
- The visible effect is the opposite of a restriction. Admin → Ecosystem → Custom EVM Chains used to refuse Avalanche and Linea as reserved built-in names, so the very half-entry that made those two chains unusable also locked operators out of adding them properly. Both are accepted there now, and a chain added through that screen is registered for address issuance, the provider, deposit monitoring and balance reading together rather than for one of the four.
Regulatory and audit claims in customer-facing copy
Several marketing claims the product made about itself were deleted outright rather than reworded.
- Removed "The 1 Regulated ITO Platform" from the token launchpad, which now reads simply "Token launchpad", and the strings "Fully Regulated", "Guaranteed", "Secure, transparent, and regulated" and "Industry-leading security, verified projects, and a seamless investment experience" from the catalogue entirely, in all ninety languages.
- Changed "All projects undergo rigorous security audits before being listed" to what the product actually does: "Every offering stays pending until this platform approves it".
- Removed the P2P page's offer to "Unlock premium benefits and lower fees", and the affiliate share message's promise of "special benefits" to the person being invited — it now says only that the referrer earns a reward.
- Operators who relied on any of that wording in their own marketing should note it is no longer in the product.