Bicrypto 6.5.4

9 July 2026

STABILITYREDISQUEUESCRONSCHEDULEREMAILSCYLLABTCEXPLORER-APIETHERSCAN-V2TX-HISTORYDIAGNOSTICSADMIN-UIDEPOSITSWITHDRAWALSBACKGROUND-SCANNERINVESTMENTSCOPY-TRADINGNFTGATEWAYFOREXMLMIDEMPOTENCYXMRSOLTRONTONTRC20BUG-FIXES

Core v6.5.4

Release Date: July 9, 2026 Tags: STABILITY, REDIS, QUEUES, CRON, SCHEDULER, EMAIL, SCYLLA, BTC, EXPLORER-API, ETHERSCAN-V2, TX-HISTORY, DIAGNOSTICS, ADMIN-UI, DEPOSITS, WITHDRAWALS, BACKGROUND-SCANNER, INVESTMENTS, COPY-TRADING, NFT, GATEWAY, FOREX, MLM, IDEMPOTENCY, XMR, SOL, TRON, TON, TRC20, BUG-FIXES

Overview

Version 6.5.4 makes the backend survive its optional infrastructure being down instead of dying with it. With Redis not running, the server used to crash-exit before it ever started listening — an infinite restart loop under pm2. It now boots and serves normally on an embedded fallback store, and switches back the moment Redis returns. An absent ScyllaDB and a mismatched Bitcoin network got the same treatment.

The largest body of work is an overhaul of ecosystem deposit crediting, driven by reports that deposits were not funding account wallets. Ten confirmed bugs could genuinely leave an on-chain deposit uncredited, and detection no longer stops the moment a user closes the deposit page. A new Chain Requirements & Diagnostics page says what each chain needs and probes it live, in one click.

It also carries a top-to-bottom audit of the cron system, which found jobs that had silently never run and timer-driven money handlers that could double-pay, under-pay or strand funds.

Update Instructions

The usual pnpm updator is all that is required.

Configuration notes:

  • Check your explorer API keys. Legacy per-chain V1 keys (old BscScan / PolygonScan / ArbiScan / CeloScan keys in POLYGON_EXPLORER_API_KEY, ARBITRUM_EXPLORER_API_KEY, etc.) are dead on Etherscan V2 — and they override the global key, so a working ETHERSCAN_API_KEY is silently shadowed by a broken one. Remove the per-chain keys unless they are genuine V2 keys. One Etherscan V2 key covers ETH, Polygon, Arbitrum, Celo and most standard chains on the free tier.
  • Add a free fallback provider key. Etherscan's free tier excludes BSC and Optimism ("Free API access is not supported for this chain"), and Fantom (chainid 250) / Cronos (chainid 25) are not on V2 at all. ANKR_API_KEY covers the most mainnets; for BSC testnet specifically use MORALIS_API_KEY or COVALENT_API_KEY (Ankr does not index chainid 97). The requirements page now shows precisely which provider serves each chain and which key is missing.
  • SCYLLA_ENABLED (optional) — set to false to skip ScyllaDB entirely on installs that do not use ecosystem spot/futures trading. Wallets, deposits and withdrawals do not depend on ScyllaDB either way.
  • MINI_REDIS_DIR (optional) — directory for the Redis-fallback snapshot; defaults to backend/storage/mini-redis/ (git-ignored, excluded from nodemon watching).
  • The Redis fallback store is in-process: keep running a single backend instance (pm2 fork mode), as already required by the withdrawal queue.
  • Background deposit scanner is on by default and needs no configuration. Optional knobs: ECOSYSTEM_BACKGROUND_SCAN=false disables it; ECOSYSTEM_SCAN_RATE_<CHAIN> (requests/second, e.g. ECOSYSTEM_SCAN_RATE_TRON=2) raises a chain's scan rate when you run your own node or a paid API; ECOSYSTEM_SCAN_INTERVAL_MS (default 120000) is the per-address rescan cadence; ECOSYSTEM_SCAN_ACTIVE_TTL_MS (default 72 h) is how long an address stays watched after the last deposit-page visit; ECOSYSTEM_SCAN_EVM_LOOKBACK_BLOCKS (default 3000) bounds the ERC-20 log sweep.
  • Pending-deposit storage migrates automatically. The legacy pending-transactions Redis blob is converted once into the new per-transaction hash (ecosystem:pendingDeposits) on first read — no manual step. Deposits that exhaust verification retries are now preserved in ecosystem:pendingDeposits:dead (inspect with HGETALL ecosystem:pendingDeposits:dead) instead of being deleted.
  • No database migrations are required.

Added

Chain Requirements & Diagnostics — everything a chain needs, testable in one click

The problem

The ecosystem overview page showed only set/unset booleans for a handful of env keys. There was no way to see what each chain actually requires — which variables, valid network names, which flows break without an optional key — and no way to test whether any of it worked short of sending a real deposit. Configuration traps (Polygon's network is matic, not mainnet; CELO_NETWORK=alfajores is invalid; a misspelled legacy ARBIRUM_MAINNET_RPC key is honored by health checks but ignored by the real provider path; SOL_NETWORK unset silently connects to devnet) were only discoverable by reading source.

What shipped

A new admin page at Admin → Ecosystem → Blockchain (linked from the ecosystem overview and its navbar), offering two things:

  • A requirements view listing the complete configuration contract for all supported chains — 12 built-in EVM chains, BTC/LTC/DOGE/DASH, SOL, TRON, TON, XMR, plus every operator-defined custom EVM chain. For each chain: every env variable the runtime reads (required vs optional, which flow breaks without it, defaults, examples, whether it is currently set — secrets are never echoed, URLs are shown host-only), valid network values with the current one validated, dead keys that no code reads (e.g. XMR_WALLET_PASSWORD, LTC_ZMQ_*) flagged so operators stop cargo-culting them, per-chain warnings for the known traps above, and non-env prerequisites checked live against the database: vault unlocked, master wallet present, license/DB toggle for extended chains, active tokens on the configured network.
  • A live diagnostics test that runs read-only checks for one chain (or PLATFORM for shared infrastructure: vault, ecosystem extension, Redis, ScyllaDB). EVM chains get RPC liveness with chain-id verification, WebSocket checks, explorer/provider probing and a 9-method RPC capability scan; UTXO chains get provider-matrix checks (mempool.space / BlockCypher / self-hosted node incl. sync state and ZMQ endpoints); SOL/TRON/TON get their real service endpoints probed with API-key validity decoding (401/403/429 explained); XMR gets both monerod and monero-wallet-rpc checked (with Digest auth) including daemon sync state. Every result maps onto a plain "Will it work?" verdict per flow — balances, deposits, withdrawals, realtime — and a test only passes when all of it holds (a missing license or master wallet fails the chain even if its RPC answers). Results are cached so they survive page reloads.

Everything on the page leads with a status pill — Ready / Issues found / Setup needed / Not tested yet — plus one plain-language action sentence, with the full variable reference and per-check details one click away.

Background deposit scanner — deposits arrive even after the page is closed

The gap

Deposit detection ran only while the user kept the deposit page open (plus a 2–10 minute grace period) — on every chain except BTC, which had its own cron. Funds sent after closing the tab sat undetected until the user happened to reopen the deposit page; for some paths they could be missed entirely. The old ENABLE_DEPOSIT_MONITORING background loop turned out to be dead code — it was never invoked anywhere, and its filter could not detect ERC-20 transfers even in principle.

The design: bounded working set + hard rate caps

The new scanner deliberately does not sweep every wallet forever — that is exactly what breaks free-tier API limits as user counts grow. Instead:

  • Working set: every time a user opens a deposit page, that address is registered into a Redis due-queue and stays watched for 72 hours after the last visit, rescanned roughly every 2 minutes. Scanning cost tracks recently active depositors, never total registered wallets.
  • Rate limits respected by construction: every scan request passes through a per-chain token bucket sized for free public endpoints (Monero's serialized wallet-rpc gets one sweep per 20 s; BlockCypher-backed chains share conservative rates). When many addresses are active, the sweep simply takes longer — request rates can never exceed the cap, so more users degrade detection latency, never trigger 429 bans.
  • One scan pass per chain, reusing proven code: each chain's monitor gained a one-shot sweep — EVM native via explorer history, ERC-20 via bounded log sweeps, UTXO via confirmed-transaction replay, Solana native + SPL (scanning the associated token account), Tron native + TRC-20, TON, and XMR (skipped while a live session already monitors that wallet). All crediting flows through the existing pipeline, which is idempotent — the scanner, a live page monitor and the BTC cron can all see the same transaction without double-crediting.
  • Exactly one scanner runs across all processes (Redis lock with takeover if the holder dies), supervised by a cron tick that restarts the loop if it ever crashes.

The result: a user can request a deposit address, send funds from an exchange an hour later with the browser closed, and the deposit is detected, confirmed and credited automatically — on every chain.


Changed

The backend now survives Redis being down

What used to happen

With Redis unreachable, startup died in three stacked ways. The email queue is created at module load, so its three eager Redis connections began retrying every 50 ms–2 s before initialization even started — that was the [EMAIL]/[QUEUE] log storm. Initialization then crashed outright: the settings cache only fell back to the database when Redis returned an empty result, not when it errored, so the binary-orders pre-check threw, initialization exited the process, and pm2 restarted it to do it all again. And even with that fixed, cron registration would have hung forever — BullMQ waits for a ready queue indefinitely. Per-request paths had the same blind spot: logins died with raw 500s because sessions live in Redis.

The embedded fallback store

A new in-process store implements the exact command surface the platform uses — strings, hashes, sets, sorted sets, TTLs, SET NX locks, pipelines/multi, and the redlock locking scripts — behind the same client interface. A resilient proxy sits in front of the real Redis client: while Redis is reachable every command passes through untouched; the moment it is not, commands are served by the embedded store instead. Failover is fail-fast (no 5-second hangs per request), reconnection attempts back off exponentially to 30 s instead of hammering every second, and the log tells you about state transitions once — one line when the fallback engages, one when Redis is restored — instead of one line per retry.

The store persists to disk (debounced snapshots), so sessions, locks and working sets created during an outage survive a process restart. When Redis comes back, fallback data is replayed into Redis fill-gaps-only — keys that already exist in Redis are never overwritten — and the proxy switches back automatically.

Queues degrade instead of dying

Bull and BullMQ fundamentally require a real Redis (their logic is server-side Lua), so queue-backed features degrade explicitly while Redis is down:

  • Emails and notifications are sent inline — immediately, in-process — instead of being queued. The queues themselves are now created lazily and only while Redis is up (no more eager connections at import time), and jobs queued before an outage are drained when Redis returns.
  • Cron jobs run on plain in-process timers at their configured intervals, with per-job overlap protection, and migrate back to BullMQ automatically on recovery. A job can never run in both modes at once. Waiting for the queue to become ready is capped at 10 seconds, so cron registration can no longer hang startup.
  • Settings and extensions fall back to the database on any cache error — the boot crash path is gone at the source.

Money-touching paths were audited and already failed closed (P2P trade locks return a clean 503 before any funds move; deposit crediting retries on the next monitor poll) — that behavior is preserved. The one intentional gap: active sessions live in Redis, so users logged in at the moment Redis dies sign in again; sessions created during the outage survive, even across restarts.

ScyllaDB: fast skip instead of a 64-second retry wall

ScyllaDB backs ecosystem spot/futures trading only — wallets, deposits and withdrawals never touch it. Yet an absent ScyllaDB cost every boot five exponential connection attempts (2 s → 64 s of waiting) and a dozen error logs before the ecosystem extension gave up. The client now runs a 1.5-second TCP probe first: if nothing is listening, it logs one clear warning and skips connection retries entirely — the Extensions phase completes in ~2 seconds instead of ~64. Installs that do not use eco trading can set SCYLLA_ENABLED=false to skip even the probe.

EVM transaction history: the whole provider chain is tested, not just Etherscan

Background

Etherscan retired all per-chain V1 explorer APIs and consolidated on a single multichain V2 endpoint (one key, chainid parameter). The platform's tx-history layer already handled this with a fallback chain — Etherscan → Ankr → Moralis → Covalent → NodeReal, order configurable via TRANSACTION_PROVIDERS — but the first iteration of the diagnostics only tested Etherscan, so a chain happily served by a fallback provider still showed a red "Explorer API" check, and a chain with a dead legacy key looked like an Etherscan outage.

What changed

The requirements page now probes every provider in the runtime's own priority order for each EVM chain, with the same chain/network support rules the runtime uses (including testnet awareness — e.g. BSC testnet is probed as chainid 97). The explorer check reports each provider as one of: active (this one will serve the chain), ok (works, in reserve), no key, not supported, or failed (with the provider's actual error, such as Etherscan's free-tier message). Readiness ("Will it work?") passes when any provider works. Per-chain warnings now encode the V2 reality: BSC/Optimism are paid-tier on Etherscan, Fantom and Cronos are not on V2 at all, and Cronos has no fallback provider — its tx history is effectively unavailable.

Redesigned diagnostics page

The Chain Requirements & Diagnostics page was rebuilt to lead with answers instead of data. Each chain now shows a single status — Ready / Issues found / Setup needed / Not tested yet — plus one plain-language sentence stating the most important next action (e.g. "Set ETH_MAINNET_RPC in .env, then restart the backend" or "Everything looks good — served by Ankr"). Details are still one click away: the provider pills row highlights which service is serving the chain, missing required variables surface first with copy-ready key names, and the rest of the environment reference sits behind a "Show all variables" toggle. Everything functional carried over: per-chain tests, "Test all chains", the platform-infrastructure test, and cached last-test results.


Fixed

Bitcoin scanner: no more wrong-chain queries

BlockCypher only serves Bitcoin mainnet and testnet3. With BTC_NETWORK=testnet4 (or signet), the provider used to log a warning and then query testnet3 anyway — an entirely different chain. In practice that meant endless HTTP 400: address incompatible with current block chain errors every scan cycle; in principle it risked treating wrong-chain data as real. The provider now refuses unsupported networks outright, the scanner's fallback chain moves on (mempool.space and a local Bitcoin Core node both support testnet4), and if no provider can serve the configured network the scanner stops cleanly with a configuration hint instead of erroring forever.

Fatal crashes now say why

The global exception handler logged the fatal error through the buffered live console and then exited — the buffered line was never flushed, so the terminal showed [nodemon] app crashed (or a pm2 restart) with no reason at all. The handler now writes the full stack directly to stderr before anything else, so every future crash is diagnosable from the console output.

Ecosystem deposits: every "deposit never arrived" path fixed

An investigation into user reports that "deposits are not funding account wallets / deposits end up in the funding wallet" traced every deposit path — detection, pending verification, crediting — across all chains. The verdict, and what changed:

"It went to the Funding wallet" is correct behavior, badly explained

There is no separate "funding" wallet type. Ecosystem deposits always credit the ECO wallet — every path resolves the target as an ECO wallet explicitly, so crediting a Spot or Fiat wallet is impossible. But the platform labels that same wallet "Funding" on some screens (wallet options, P2P, PnL) and "Eco" on others, and the spot trade panel on standard markets reads the SPOT balance — which shows 0 after a perfectly successful eco deposit. Users reasonably concluded their deposit vanished. The deposit success screen now states exactly where the funds landed, deep-links to that wallet (/finance/wallet/ECO/{currency}), and for eco deposits shows a one-tap "Transfer to Spot" shortcut with an explanation.

Crediting core

  • Lost-update race eliminated. All pending (detected-but-unconfirmed) deposits lived in a single Redis JSON blob that every writer rewrote wholesale; a verification pass writing back a stale snapshot could silently erase a deposit detected moments earlier — permanently. Pending deposits are now stored one Redis hash field per transaction (atomic add/remove per tx), so concurrent detection, the 10-second worker and the 1-minute cron can no longer clobber each other. Existing data migrates automatically.
  • No more destructive eviction. A pending deposit used to be deleted after 5 failed verification attempts — roughly 50 seconds of RPC or database trouble (or an admin-disabled wallet) permanently destroyed the only record of a real on-chain deposit, with nothing but a log line left. The retry budget is now 30 attempts, and anything that still exhausts it moves to a dead-letter store (ecosystem:pendingDeposits:dead) where support can inspect and replay it. A real deposit is never again reduced to a lost log line.
  • Withdraw-to-your-own-address now credits. The deposit dedup check matched any prior transaction with the same hash and wallet — including the WITHDRAW row of a self-send — so the incoming leg was rejected as "already processed" while the UI showed Deposit confirmed. The dedup now only matches prior deposits; genuine double-credits remain impossible via the wallet service's idempotency keys.
  • Deposit addresses can no longer outrun their monitoring. Address issuance and deposit monitoring used two different token/network eligibility rules, so a user could receive a perfectly valid deposit address whose deposits could never be detected (strict network mismatch → "Token not found" when monitoring started). Both paths now share one matcher.

Per-chain monitor fixes

  • XMR: the monitor self-terminated after ~12–15 minutes — before Monero's required 6 confirmations (~15–20+ min) could complete — so anyone who closed the tab after sending never got credited. The retry cap is now idle-only: the monitor keeps watching while a deposit is confirming, bounded by a 90-minute wall clock.
  • Solana: the deposit listener was one-shot with no history replay — a second deposit arriving while the first was processing was dropped forever, a transient RPC failure permanently discarded a real deposit while reporting it handled, versioned (v0/ALT) transactions from exchanges crashed the parser, and after 1 hour idle the monitor silently watched nothing. The monitor now replays recent address history on every (re)start, retries transient failures, parses v0 transactions correctly, and signals the session layer when it times out so it is re-armed.
  • Tron: stopping a deposit session never actually stopped its 30-second TronGrid poll loop, so pollers accumulated for the life of the process until rate limits killed all Tron detection. Stops now tear the loop down. Separately, the TRC-20 amount parser took the first Transfer event in a transaction — wrong recipient or wrong token in batch/multisend transactions, with over- or under-crediting possible — and lost precision above 2⁵³ raw units. It now matches the Transfer by recipient and token contract with BigInt-exact amounts.
  • EVM: a WebSocket that closed cleanly (idle timeout, load-balancer drop — no error event) silently stopped ERC-20 deposit detection while the UI still showed monitoring as active. Clean closes now trigger the same automatic reconnect as errors.

Cron system: a top-to-bottom audit and financial-correctness overhaul

The Redis-fallback work above changed how every cron is scheduled, so the whole cron subsystem — the scheduling engine, the core wallet/order jobs, and every extension's cron handlers — was audited end to end against a single recurring report: "why do some of our scheduled jobs just not run?" The findings fall into three groups: jobs that silently never ran, timer-driven money handlers that could double-pay, under-pay or strand funds, and admin visibility that hid all of it.

Jobs that silently never ran

  • A broken extension made its crons a green no-op — forever. Every addon cron is resolved lazily through a shared safe-import helper. That helper caught any load error — a syntax error after a bad update, a missing dependency, a failure while the extension started up — returned nothing, and cached that nothing for the life of the process. The cron wrapper then resolved successfully with nothing to call, so the admin panel showed the job "completed" every interval while it did nothing, with no error anywhere. The helper now distinguishes "extension not installed" (expected, silent) from a real load failure (logged loudly, not cached — retried on the next tick).
  • The trading-bot cron suite was never wired up. Its five jobs (engine tick, stale-bot detection, strategy ratings, daily stats, cleanup) were only ever registered through a self-scheduler function that nothing called. They now run through the central cron manager like every other extension.
  • The AI-investment cron was loaded in a way that only works in development. In a built deployment its handler never loaded, silently disabling AI-investment payouts. It now loads the same way every sibling job does.
  • Two P2P maintenance jobs (trade archival, reputation scoring) existed but were never registered — now scheduled (archival daily, reputation hourly).
  • Long-interval jobs never fired while Redis was down. In the new in-process fallback mode a job's first tick was scheduled a full interval after boot, and every restart reset that clock — so on a host running without Redis, a nightly restart meant the 24-hour jobs (wallet PnL, PnL cleanup, copy-trading daily reset) and the weekly analytics job never ran even once. Each job now persists its last-run time and, on entering fallback mode, runs immediately if it is already overdue instead of waiting a fresh interval.
  • A manual "Run now" could overlap a scheduled run. The admin trigger checked a different in-memory flag than the scheduler, so triggering a job seconds before its tick could execute the same handler twice concurrently. The trigger now shares the scheduler's single-flight lock and returns a clean 409 already running instead.
  • Enabling an extension didn't schedule its crons until a restart (and disabling didn't stop them). The manager now re-syncs the addon set periodically, starting newly-enabled jobs and stopping disabled ones live.
  • A leaked duplicate backend kept running crons against the shared database. The listen callback ignored uWS's bind result, so a second process that failed to grab the port stayed alive and ran its own copy of every cron. That process now exits on a failed bind. (The multi-thread launcher's worker path, which pointed at a nonexistent file, was corrected as well.)

Alongside these: the BullMQ boot cleanup no longer discards a job's overdue catch-up tick, a Redis up/down flap can no longer leave a job double-scheduled, a dead retry-backoff option was removed, boot-time initialization is race-safe against a concurrent first caller, and the Bitcoin deposit scanner was moved under the ecosystem extension gate so it cannot credit deposits while ecosystem is disabled.

Money handlers: double-pay, under-pay and stranded funds

The reconcilers and payout jobs move real money on a timer, so every one was checked for idempotency, transaction boundaries and evidence-based decisions.

  • The spot-withdrawal reconciler could refund a payout that was actually sent. Its 24-hour safety net refunded any still-pending withdrawal it could not find on the exchange — but "not found" was also what a page of missing history or a crash-after-send looked like, so a completed payout could be refunded on top of the real send. It is now strictly evidence-based: it refunds only after a positive scan confirms no matching withdrawal exists, and escalates anything ambiguous to admin review untouched. Refunds now commit atomically with the FAILED status flip (a refund failure no longer strands the funds), pending rows are tracked for 7 days instead of 60 minutes, and rows the exchange still reports as pending are no longer demoted out of every reconciler's view.
  • NFT auctions transferred the token without moving any money. The settlement cron flipped ownership, recorded the sale, and rejected the losing bids — but contained no debit of the winner and no credit of the seller, so any auction without a deployed on-chain contract handed over the NFT for free. It additionally picked an arbitrary bid as the winner (the highest-bid sort was silently ignored by the ORM) and compared the reserve price as a string ("9" < "10" is false). Settlement now selects the true highest bid numerically, checks the reserve numerically, moves funds with idempotent ledger records mirroring the on-chain payout, claims the auction atomically so it can't double-settle against the manual endpoint, aborts cleanly if the on-chain settle fails, and — if the database write fails after the chain succeeded — retries and then raises a loud manual-reconciliation alert instead of silently stranding the sale.
  • NFT offer expiry released the wrong escrow amount. It recomputed the release from the current marketplace fee, so any fee change since the offer was placed either over-released (draining other holds) or under-released (stranding funds). It now releases exactly the amount held at offer creation, and claims the offer atomically so expiry can't race an acceptance.
  • Gateway payouts duplicated the balance. The dedup keyed on an exact timestamp window that never repeated, so instant-schedule merchants got a fresh payout for their entire pending balance every hour, and daily merchants re-claimed unapproved balances each day. Payouts now cover only the not-yet-claimed delta, computed under a row lock inside the transaction, and weekly/monthly schedules fire when due or overdue rather than only on the exact calendar day (a one-day outage no longer skips a whole period).
  • The ecosystem withdrawal queue could refund a sent withdrawal. When two workers raced the pending→processing claim, the loser was treated as a hard failure — marked FAILED and refunded — while the winner broadcast the transaction, producing a refund on top of a real send. A lost claim (409) and a vanished row (404) are now treated as a no-op hand-off; the completion write is guarded to the processing state; and non-UTXO rows with an unknown broadcast state are kept for review instead of blindly reverted (which risked a double broadcast).
  • Forex investments were cancelled without refunding the principal whenever the payout wallet was missing — a routine condition. Maturity processing now creates the wallet or refunds the principal (idempotently, under a row lock, with a conditional status flip), and only ever leaves the investment active with an alert — it never cancels without making the user whole. Duplicate runs can no longer flip a completed investment to cancelled.
  • Investment payouts paid the wrong number and mislabeled the ledger. The general and AI investment crons paid the plan's default profit figure while the purchase flow promised a percentage-based return, recomputed the maturity date instead of honoring the stored one, and recorded general-investment ROI under the AI ledger type. All three now pay exactly what the user was shown, honor the stored end date, and use the correct ledger operation type (also added to the wallet-integrity reconciler so those credits reconcile).
  • Copy-trading never distributed profits and never enforced stops. The closed-trade job credited wallets with no idempotency key — which the wallet service rejects — so every run threw and no profit was ever shared; and the stop-loss/take-profit monitor only logged "Would close trade here" and never actually closed anything. Profit distribution is now idempotency-keyed, inside its transaction, and pays into the correct wallet; the monitor closes through the real close path. Daily-limit reset and loss checks were re-anchored to a shared UTC day boundary (they previously fought each other across timezones, flapping followers between paused and active), the replication backstop now queries the status the live path actually writes (it had been polling a status nothing ever set) and only marks a leader trade replicated when at least one follower copy actually succeeded, and follower rows are now keyed consistently with the live path so cancellations tear down cleanly.
  • The AI market-maker could not be turned off. Its enable/pause/maintenance switches compared settings against booleans, but the values are stored as strings — so the string "false" never disabled the engine and "true" never paused it. All switches now coerce robustly and fail closed on a read error. Its pool rebalancer, which had been rewriting pool balances to a 50/50 split with no actual asset movement (fabricating inventory that later converted to real credits), now alerts on imbalance instead of inventing balances. The daily reset was re-anchored to UTC to stop resume-then-immediately-re-pause churn.
  • Forex swap rollover skipped whole days. It only acted during the 17:00 New York hour, so a restart spanning that window silently skipped a day's swap charges with no catch-up. It now settles the most recent past cutoff on any tick (idempotent per position per date) and backfills missed days.

Smaller correctness fixes in the same pass: the staking maturity email (which always threw on a missing field, so completion emails never sent); P2P timeout handling (errors were swallowed so the job always reported success; the reputation-milestone row was written for every eligible user on every run); MLM referral rewards (duplicated across period boundaries and collapsed multiple referrals into one — now deduped per referral per calendar period, with end-of-period transactions no longer dropped); mailwizard campaigns (a crash mid-send re-sent already-delivered emails; a zero send-speed stalled forever); the BTC scanner (credited outgoing spends as deposits, died permanently on one transient provider error, and hammered the public API per-wallet — now direction-checked, self-healing and rate-limited); the fiat-rate and currency jobs (silently reported success when every provider failed); and expired-user-block processing (force-activated users regardless of why they were inactive — now only lifts the specific expired block).

Admin visibility

  • The admin cron WebSocket stream was completely unauthenticated — any guest could subscribe to live job status and error broadcasts. It now requires the cron admin permission like the REST endpoints.
  • The dashboard's "Last Run" always showed "Never", the timeline fabricated random durations when a real one wasn't present, and a manual trigger that loses the single-flight race now surfaces a clear "already running" conflict instead of a generic failure.

Also fixed

  • The blockchain admin page returned a 500 on older builds. A request for an unknown blockchain surfaced the internal "System configuration error" with a 500; it now returns a proper 404 Blockchain not found — which is also what the new requirements page falls back to on builds that predate it.