Bicrypto 6.4.0

26 May 2026

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

SECURITY2FAKYCICOWALLETBUILDDOCSACTIVITY-FEEDBUG-FIXESMIGRATION

Core v6.4.0

Release Date: May 26, 2026 Tags: SECURITY, 2FA, KYC, ICO, WALLET, BUILD, DOCS, ACTIVITY-FEED, BUG-FIXES, MIGRATION

Overview

Version 6.4.0 is a security, compliance, and observability release. The biggest single addition is a real per-user activity log — sign-ins, 2FA changes, API key lifecycle, KYC status transitions, password resets and profile edits are now recorded and surfaced on the dashboard. Multiple US-only / dev-only code paths were generalized (phone numbers, KYC document storage, gateway plugin downloads, the WalletConnect docs entry). A number of money-touching bugs were closed in ICO and the admin withdrawal flow.

Update Instructions

After updating, run the following command in terminal:

pnpm updator

Upgrade Notes

Compliance — Indian FIU-IND inquiries

A long-form, honest breakdown of what BiCrypto provides today versus what an Indian VDA SP needs for FIU-IND / PMLA compliance has been circulated to support, in response to operator inquiries about going live in India. KYC framework, custody, and core wallet infrastructure are in the product. PAN / Aadhaar / Bank verification, sanctions and PEP screening, blockchain analytics, Travel Rule, and FIU-IND STR/CTR FINNET XML reporting are vendor integrations or custom-build line items, not in-product features. No code change — this is set-expectations material for operators considering an India launch.


Added

Real per-user activity feed

A new activity log captures meaningful per-account events as they happen — sign-ins (including which method), 2FA enabled/disabled, password resets, email and phone verifications, the full API-key lifecycle, KYC submissions and admin status transitions, profile edits, and Web3 wallet connect/disconnect. Each entry records the action, an IP and user-agent, a severity (success / warning / info) for UI styling, and free-form metadata for context.

What's logged

EventSeverity
Sign-in (password, Google, Web3 wallet, mobile)info
Failed sign-in (wrong password)warning
Sign-outinfo
2FA enabledsuccess
2FA disabledwarning
Password resetwarning
Email verifiedsuccess
Phone verifiedsuccess
API key createdsuccess
API key updatedinfo
API key deletedwarning
KYC submittedinfo
KYC resubmittedinfo
KYC approvedsuccess
KYC rejectedwarning
KYC needs more informationwarning
Profile updatedinfo
Web3 wallet connectedsuccess
Web3 wallet disconnectedwarning

Dashboard card

The Recent Activity card on the user dashboard now fetches the live feed on load and renders type-appropriate icons (key, shield, mail, link, etc.), severity-styled backgrounds, and a real relative timestamp from the actual event time. A small spinner sits in the card while loading. If a brand-new account hasn't accumulated any events yet, a short summary derived from profile state is shown so the card isn't empty — as soon as anything is logged the real feed takes over.

Design choices worth knowing

  • All logging is fire-and-forget. A logging hiccup can never block a sign-in, withdrawal, or any other user action.
  • Admin KYC status changes write to the applicant's feed, not the admin's, so the user sees "KYC approved" in their own dashboard.
  • Failed-login attempts are only logged when the email matches a real user. Logging unknown-email attempts would let the feed act as a user-enumeration oracle.
  • KYC admin-notes-only edits don't log; only status transitions do, otherwise the feed fills with back-office churn the user wouldn't recognize.
  • Withdrawals and deposits are deliberately not in this feed — they already have dedicated transaction-log surfaces, and API-key per-call telemetry already exists in its own audit table.

WalletConnect setup docs — now reachable

The WalletConnect setup guide already existed in the docs content tree (full walkthrough with Reown account creation, project ID, environment variables, production notes, troubleshooting) but it had no sidebar entry and no route mapping. The guide was effectively orphaned: not in the sidebar, not in search results, and the direct hash URL didn't load.

  • Added a sidebar entry under Core Configuration and registered the address. After redeploying the docs static files the guide is reachable from the navigation and directly via its hash URL. No content rewrite was needed.

Fixed

2FA setup — email no longer renders the authenticator-app QR screen

The setup step had a single branch that rendered the same QR-code-plus-three-step screen for both Email and App. Selecting Email Authentication showed a QR code labeled for an authenticator app. Email now has a dedicated setup card that shows the user's email address and explains a verification code will be sent.

2FA setup — SMS phone number is now editable

The SMS path had two problems that combined to make the option unreachable for most users:

  1. Selecting SMS was rejected up front with a toast when the account had no phone number on file — so any user without a profile phone couldn't even reach the setup screen.
  2. Once they reached it, the phone input was disabled with no way to enter a new number.

Now: selecting SMS skips the early validation and goes straight to the setup screen. The phone input is editable, pre-fills from the profile when available, and the SMS is dispatched on the Send Verification Code button using whatever the user typed in. Resend uses the entered number too.

2FA setup — email is no longer sent before the user clicks Continue

Selecting Email on the method-select step used to dispatch the verification email immediately, before the user had agreed to anything — and the next screen then claimed "We've sent a verification code…" The OTP email is now sent only when the user clicks Send Verification Code on the Email Authentication card. Copy was changed to future-tense so it doesn't lie about what already happened.

2FA setup — success toasts silenced

A generic "Success" toast was flashing after every step in the OTP flow. Errors still surface, but the green confirmation on routine intermediate calls is gone. Explicit feedback like "Code Resent" is kept where it was already there.

OTP email recipient is no longer empty

The verification email was being addressed to an empty recipient and failing at the SMTP layer with "No recipients defined" before retrying twice and failing permanently. The account is now loaded properly so the recipient and template resolve correctly; if no email address can be determined the request fails fast with a clear 400 instead of silently queuing a broken send.

Phone verification — international support

The phone tab on the security profile page was hard-coded for US numbers. Typing a + got stripped on the next keystroke, the formatter clamped input to 10 digits, the validator only accepted (xxx) xxx-xxxx, and the send call prefixed +1 to the digits regardless of country.

All four hard-codings are gone. The formatter now keeps a leading + and up to 15 digits. Validation accepts + followed by 7–15 digits, so country codes from anywhere (Kenya, UK, Germany, India, etc.) work. The number is sent exactly as entered. Placeholder and input length were updated to match.

KYC documents no longer 404 in admin

The KYC upload endpoint was deciding where on disk to write each file based on the NODE_ENV environment variable. On production deployments where NODE_ENV wasn't explicitly set (ad-hoc starts, PM2 configs that do not load the project's environment, etc.) the upload was writing to a directory one level above where the static-file server was reading from. Files landed successfully but at an unreachable path, so admins reviewing KYC saw broken thumbnails.

Resolution is now automatic: the endpoint probes the candidate directories the static server already understands, picks the first that exists, and falls back to the layout matching the current working directory if nothing exists yet, so the first upload creates the directory in the right place. A startup log line names the resolved path so any future 404 is one line away from being diagnosed.

KYC extension whitelist mismatch fixed

The static-file server's allowed-extensions list for uploads was missing .doc, .docx, .xls, .xlsx, and .csv — extensions the upload endpoint accepts. So users could submit a .docx ID document, it would land on disk, and every admin request for it returned 403 Forbidden. The two lists now match.

ICO transaction details — correct numbers

Two transaction-detail pages were doing the wrong arithmetic on stored purchase records, dividing where they should have multiplied and labelling a token count as a dollar figure. A $20 purchase at $0.005 per token displayed as $4,000 · 800,000 tokens. The numbers were internally consistent, so the bug looked plausible, but every displayed number was inflated by the same factor — one divided by the token price.

Both the admin and user-facing transaction detail pages now show the correct USD invested and the correct token amount.

Admin withdrawal log — "Amount must be a finite number"

Rejecting or completing a pending withdrawal from the admin panel was failing with "Invalid amount: Amount must be a finite number" referencing a spot withdrawal. The handler was blindly overwriting the stored transaction with whatever was in the request — and if the request omitted the amount (an older frontend, a hand-made call, a status-only update), the amount became undefined, and the refund step then crashed when it tried to credit the customer's wallet.

Three layers of defense were added:

  1. Each editable field is overwritten only when the request actually provides it. Missing fields keep their existing values.
  2. Amount and fee are validated up front; invalid input returns a clear 400 ("Invalid amount: must be a positive finite number" / "Invalid fee: must be zero or a positive finite number") instead of cascading into a confusing wallet error.
  3. The refund step itself sanity-checks the transaction amount before touching the wallet. If the stored record is bad, it returns "Cannot refund: transaction amount is invalid. Enter a valid amount before rejecting." instead of the generic error.
  • Fixed the admin fee field loading the literal text "null" into the input when the stored value was empty, which then round-tripped back to the server. Amount and fee are now both coerced to a number on load, defaulting to "0" if missing.

Gateway integration plugin download — plugin folder is now found in production

The download endpoint was resolving the plugin source folder from the working directory of the backend process. That works in development but breaks under PM2 in production, where the working directory is the project root rather than the backend folder, so the path resolved to a location that didn't exist. The endpoint returned 404, and the frontend swallowed it into the generic "Failed to download plugin. Please try again." toast.

The endpoint now probes candidate directories in priority order (the typical development, production, and standalone layouts) and picks the first that exists. The resolved path is logged on startup so misconfiguration is obvious.

Gateway integration plugin download — server errors are now specific

If the plugin tree is genuinely missing from the deployment, the endpoint returns a deployment-specific message naming the directory it tried, instead of a generic 500. If a single plugin can't be found, the 404 includes the path that was checked.

Gateway integration plugin download — the page surfaces the real reason

The plugin download page no longer always shows "Failed to download plugin. Please try again." When a request fails it now shows the server's own error message — so when something goes wrong the operator sees what went wrong without opening developer tools.

Build — production frontend builds no longer fail on wagmi

Production frontend builds started aborting with "Can't resolve @wagmi/core/tempo". A peer-dependency escalation from one of the Reown packages was pulling in a newer wallet-connector package than the rest of the stack expected, and that newer version imports something the installed version does not provide.

@wagmi/core and @wagmi/connectors are now pinned to the versions the rest of the stack was already proven against. The first deploy after this change needs the lockfile regenerated so the new pins propagate.