Bicrypto 6.4.5
16 June 2026
Core v6.4.5
Release Date: June 16, 2026 Tags: XMR, MONERO, WITHDRAWALS, WALLET, DATABASE, SECURITY, BUG-FIXES
Overview
Version 6.4.5 is a stability and money-safety release centered on the Monero (XMR) withdrawal path. The withdrawal executor has been hardened against the two ways a single-wallet RPC chain can lose money — re-broadcasting a withdrawal that already went out, and refunding a user for coins that are already on-chain — and now treats Monero's protocol-level output locking as the transient condition it is instead of failing perfectly valid withdrawals. Alongside the wallet work, this release makes database schema sync survive large schemas without hanging, lets delete-guard error messages reach the client instead of being masked as a generic 500, and makes ecosystem-token seeding idempotent across updates so a core update no longer trips a "Validation error."
Update Instructions
After updating, run the following command in terminal:
pnpm updatorFixed
Monero (XMR) withdrawals
No double-spends on retried or requeued withdrawals
The XMR withdrawal executor can be driven more than once for the same transaction — by the withdrawal-queue watchdog, by an earlier attempt that is retried, or by a re-pointed wallet. Previously nothing stopped a second run from broadcasting the transfer again. The executor now checks the transaction's state before building any transfer: if it is already COMPLETED or already carries a transaction hash, it stops without re-sending. The Monero relay call is also treated as non-idempotent — it runs as a single attempt (no blind retry) so a slow-but-successful broadcast can't be duplicated by an automatic retry.
Failure handling never refunds coins that already left
The failure path that marks a withdrawal FAILED and triggers a refund now checks for an existing transaction hash first, so a withdrawal that already broadcast is never overwritten to FAILED and the user is not refunded for coins that are already on-chain. The same guard is applied to the "insufficient funds" branch.
Lost broadcast responses go to manual review, not an auto-refund
If the relay request is sent but its response is lost — a timeout, ECONNRESET, socket hang-up, or refused connection — the transaction may already be on the network even though we never saw the confirmation. Instead of refunding (and risking paying out twice), the withdrawal is marked TIMEOUT with a "broadcast status unknown — manual review required" note and is not refunded, so an operator can reconcile it against the chain.
Locked funds wait for the watchdog instead of self-requeuing
Freshly received Monero outputs are protocol-locked for roughly 10 blocks (~20 minutes), independent of the platform's own confirmation count, so a wallet can legitimately show a balance while too little is unlocked to send. The old behaviour self-rescheduled on a timer, which bypassed the queue's single-owner guard and could spawn a second concurrent executor for the same withdrawal — a double-spend risk. The withdrawal now simply stays PENDING and is left for the withdrawal-queue watchdog to retry under the single-owner lock. The same transient-lock case is detected when the fee-estimation test transfer reports "not enough unlocked money" while the total balance is sufficient — it is held and retried rather than failed and refunded. A hard ceiling (2 hours) bounds the wait so a genuinely stuck withdrawal eventually fails cleanly and refunds.
Correct unlocked-balance accounting for admin profit
The admin-profit destination is an additional output spent from the same unlocked funds, so the pre-flight check now requires the withdrawal amount plus the admin profit to be unlocked, rather than the withdrawal amount alone. This stops a withdrawal from passing the balance pre-check only to fail at transfer time when the profit destination is added.
Balances now sync before they're read
Reading an XMR wallet balance previously waited on a fixed sleep, which does not sync the wallet — a freshly opened wallet returns its stale on-disk cache, which is why the admin UI could show 0 while the daemon and CLI saw the funds. The balance read now performs an explicit wallet refresh (and waits for it to complete) before reading the balance, so the reported figure reflects funds received since the last sync.
wallet-rpc is pointed at the right daemon automatically
monero-wallet-rpc only syncs against the daemon it was told to use. If it was launched without --daemon-address/--daemon-login (or against the wrong network), refresh fails with -38 "no connection to daemon" even when the backend itself can reach the daemon. On initialization the backend now re-points wallet-rpc at its own configured daemon address and credentials, making the backend authoritative over wallet-rpc's connection. A wallet refresh that hits a daemon error (-9 busy, -38 no connection) re-points the daemon and retries once, recovering from wallet-rpc starting before monerod, a daemon restart, or a mis-launched wallet-rpc.
Wallet close moved inside the queue lock
The wallet was being closed outside the queue, which raced the next queued open/close on the single-wallet RPC and produced spurious -13 "No wallet file" and wrong-wallet close errors. The close now happens while the single-wallet lock is still held, and the now-rare benign -13 on refresh is demoted to a debug log to cut noise.
Database and platform
Schema sync no longer hangs on large schemas
On a schema with hundreds of related tables, the startup schema sync could appear to "hang forever" — the dominant cost is MySQL's per-foreign-key validation, and it commonly stalls on a metadata lock. Schema sync now runs with foreign-key checks disabled for its duration and always restored afterward, even if the sync throws. The resulting schema is identical to a plain sync — only the validation cost during the ALTER statements is skipped. This runs at startup only, on the single main thread.
Delete guards return their real error
A delete guard that throws an explicit client error (a 4xx, e.g. "this record is still in use") was being swallowed and reported to the client as a generic 500. Single and bulk delete now re-throw any error carrying a sub-500 status code unchanged, so the guard's actual message and status reach the caller; genuine internal failures still surface as a 500.
Idempotent ecosystem-token seeding
The ecosystem-token seeder deduplicated tokens on a different key from the one the unique database index uses. On a core update, token metadata can drift — a contract keeps its address but gets a renamed symbol in the token list — which let the row slip past the dedup filter and then violate the unique index on insert, surfacing as a generic "Validation error." Deduplication now uses the same key as the index (case-normalized), also drops duplicates within the seed file itself, and ignores duplicates on insert as a final safety net, keeping the seed idempotent across updates.