Bicrypto 6.6.5

13 August 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

WITHDRAWALSFEESTREASURYDEPOSITSPAYMENT-GATEWAYSWALLETSRELIABILITYHIGH-AVAILABILITYBUG-FIXES

Core v6.6.5

Release Date: August 13, 2026 Tags: WITHDRAWALS, FEES, TREASURY, DEPOSITS, PAYMENT-GATEWAYS, WALLETS, RELIABILITY, HIGH-AVAILABILITY, BUG-FIXES

Overview

A money-path release: four withdrawal-fee defects, and three doors that took a payment nobody could credit. The fee defects all fail the same silent way — the customer is charged correctly and the platform's own treasury is credited nothing, with no error anywhere, because fee collection is written never to throw. One of them needs no bad actor at all: a withdrawal method with a field titled "Fee" was enough.

The deposit side is worse, because the customer has already paid by the time it shows. A payment gateway whose confirmation webhook cannot run, a spot exchange this install has no keys for, and a blockchain with no working endpoint were all offered as ways to deposit. In each case the money left the customer and had no route back to their balance.

Two reliability repairs ship alongside: a matching engine killed hard used to leave the deployment with no matcher at all, and a background timer could keep short-lived processes alive forever.

Read the Upgrade Notes before updating. Two entries ask you to go and look at records you already hold, and one changes which gateways your customers are offered.

Update Instructions

pnpm updator

Restart the backend afterwards. No database migration and no script to run.

PayFast operators, read the payment-gateway entry in Upgrade Notes first. If you run PayFast without a passphrase configured, it disappears from your deposit form on this release — deliberately, because in that state it could take a payment it had no way to credit.

Operators running Ecosystem should read Ecosystem v6.4.1 as well: it carries the address-issuance half of the deposit repair below, and asks you to check for stranded deposits.


Upgrade Notes

Withdrawal fees were charged to customers and never credited to you

Four separate defects, all landing in the same place: the customer pays, the treasury receives nothing, and nothing errors. Fee collection deliberately never throws — a failure to book revenue must not fail a customer's withdrawal — so every one of these was invisible.

A withdrawal method's own field names could overwrite the platform's figures. Custom fields arrive as request body and were merged onto the record after the platform wrote its own numbers, so a field named fee replaced the real charge with whatever it contained. This does not require a malicious client: field names are derived from their titles, so a method configured with a field titled "Fee" — or "Net Amount", or anything colliding with totalAmount — did it through ordinary administration. The customer was still debited the true amount.

A fee of zero beside a real fee resolved to zero. The settlement read metadata.fee and fell back to the fee column only when the first was absent — and a row can legitimately carry a zero there next to a populated column. When it did, settlement booked nothing and returned. That shape is reachable from the admin withdraw log, which lets an operator set the fee on a row written without one and settle it in the same request.

Every ecosystem fee ever written is unattributable. Fee credits on crypto withdrawals were written with no reference back to the transaction that produced them, while the spot and fiat paths always wrote one. That reference is how revenue is reconciled to the customer transaction, and it is what the reversal looks the fee up by — so an ecosystem withdrawal's fee could be neither traced nor reversed.

The reversal carried its own copy of the rule. When a dispatched payout later failed, the refund path re-implemented how to read the fee instead of using the same resolver that booked it. Two copies of one rule drift the moment either is corrected, and a reversal that reads a different field than the booking leaves the difference minted in the treasury.

  • Fixed a withdrawal method's custom fields overwriting the platform's own fee, total and net figures on the withdrawal record.
  • Fixed a zero metadata.fee beside a populated fee column resolving to zero, so settlement booked no revenue at all.
  • Fixed ecosystem fee credits being written with no reference to the withdrawal that produced them.
  • Changed the fee reversal to read the fee through the same resolver that books it, so the two can no longer disagree.
  • Review your settled withdrawals. Fees lost to these defects were never booked and are not recovered by updating — the customer was charged correctly, so no customer is owed anything, but your revenue figures for the affected period understate what you collected. Ecosystem withdrawals are affected for as long as that path has existed.

PayFast is hidden until its passphrase is set

PayFast signs both of its confirmation paths with the passphrase. Without it the ITN webhook refuses every delivery and the browser-return check refuses too — and the return check's own error message points the operator at the webhook, which is equally shut. There is no third route. A PayFast deposit on an install with no passphrase could only ever take money the platform was unable to credit.

It is now treated as a required credential, which means PayFast leaves the deposit form until it is configured, exactly as any other gateway missing a required key does. This is the same call the platform already makes for Adyen's HMAC key.

  • Changed the PayFast passphrase to a required credential. If you run PayFast and have not set APP_PAYFAST_PASSPHRASE, PayFast will no longer be offered to your customers. Set it under Admin → Finance → Deposit Gateways and restart; the gateway returns immediately.
  • Check whether you have uncredited PayFast payments. If PayFast has been live without a passphrase, payments completed at PayFast and were never credited here. They will not appear as failed deposits, because nothing reached the platform to fail.

Klarna, Authorize.Net and TransFi stay available, and now say what is wrong

These three also refuse their confirmation webhook without its secret, but unlike PayFast they settle when the customer returns to the site. A missing secret there is a degraded state, not a dead one — the common path still credits, and hiding a working deposit door would cost more than it saves. The exposure is the customer who pays and closes the tab before returning.

So they remain on the form, and the platform now says so plainly instead of reporting them as fully configured. The admin console carries a new state for it, the setup guide no longer files these keys under a heading reading "the gateway works without these", and the backend log names the gateway and the exact variable at error level.

  • Added a second readiness signal for deposit gateways covering the confirmation leg, separate from whether the gateway can take a payment.
  • Set APP_KLARNA_WEBHOOK_SECRET, APP_AUTHORIZENET_SIGNATURE_KEY or APP_TRANSFI_WEBHOOK_SECRET if you run those gateways and the console now flags them. Until you do, a customer who pays and does not return to the site is not credited.

A spot withdrawal could take money out of a wallet nobody could settle

Some exchanges answer a currency catalogue without credentials. On those, an install with no operator API keys showed customers a complete, healthy-looking spot deposit and withdrawal network list.

The withdrawal case is the one that cost money. Automatic approval defaults to manual, so submitting a withdrawal did not call the exchange at all — it debited the customer's wallet and wrote a pending row. The payout that row waited for is made by the admin approval step, which does call the exchange, and which was shut on the same missing credential. The customer's balance was gone, the payout could not be made, and the only way out was a manual refund.

Both directions are now refused at the network list, before anything is debited, with a message naming the exchange.

  • Fixed a spot withdrawal debiting a customer's wallet into a pending row that could never be settled, on installs whose exchange API keys are unset.
  • Changed the spot deposit and withdrawal network lists to refuse with a named error when the configured exchange cannot authenticate.
  • Review your pending spot withdrawals if your exchange credentials have been unset or invalid. Any pending row created in that period was debited from the customer and never dispatched.

Changed

Deposit gateways

  • Changed the admin Deposit Gateways console to show which gateways can take a payment but not confirm one, with the missing variable named on the card, on the detail page and in the setup guide's endpoint panel.
  • Changed the log line for hidden gateways to name the missing variable rather than only the gateway, so an operator is not left to work out which of six keys is the one.
  • Added PayU's three URL override variables to its credential list. The integration has always read them; the console could not show them, so an operator who needed to redirect PayU's success, failure or cancel URLs had no way to discover they existed.

Deposits

  • Changed the deposit currency and network lists to require a blockchain endpoint this deployment can reach, not merely a matching network name. A currency keeps its place as long as one of its chains is monitorable.

Fixed

Withdrawal fees

  • Fixed four defects that each charged the customer correctly and credited the platform nothing, described in full under Upgrade Notes.

Reliability

  • Fixed a matching engine that lost the leader lease at startup never becoming the leader again. A process killed hard — out of memory, a crashed thread, a kill -9 — leaves its lease behind for about twenty seconds, and a supervisor restarts the replacement immediately, so the replacement started inside that window and stayed a read-only follower for the rest of its life. The deployment had no order matching, no position marking and no liquidation, while looking entirely healthy. A follower now watches for the lease and takes over when it comes free.
  • Fixed the WebSocket heartbeat keeping a process alive after its work was done. The timer was started at module load and never held onto, so it could not be stopped, and merely importing that module — which most notification, support and wallet routes do indirectly — was enough to arm it. A short-lived process such as a one-off script or a migration could never exit on its own. The heartbeat fires exactly as before; it is simply no longer a reason by itself for the process to stay running.