Currencyinfo 4.2.0 rebuilds the rate source layer around keyless providers after two upstream free tiers disappeared, hardens the runtime and the container, and ships a documentation site and a real test suite.
Security audited by cryptofoundry.
⚠️ Upgrading from 4.1.2 or older
Two operator-facing steps are mandatory. API consumers need no change: /get, /getHistory and /status response shapes are unchanged.
1. A stock 4.1.2 config.jsonc does not start on 4.2.0. CryptoCompare and CoinGecko now both require an API key, and both are enabled in the 4.1.2 template, so an untouched configuration fails validation before the HTTP port opens. Disable them or supply credentials, add the keyless replacements to restore coverage, and update priorities.
2. The tickers indexes are rebuilt. Three date-ordered indexes replace three older ones. Mongoose creates the new ones on connect but never drops the old ones, so a direct upgrade builds all three at startup and can delay readiness. Build them out of band first.
Measured on two production deployments holding ~238 million ticker documents each:
| NVMe, 12 cores, 64 GB RAM | SATA, 4 cores, 16 GB RAM | |
|---|---|---|
| Build time, all three indexes | 17 minutes | 50 minutes |
indexSize before → after |
9.1 → 19.3 GB | 8.7 → 18.9 GB |
Full procedure: configuration migration and index rebuild.
Node.js 22.12 or newer is now required. Rollback to 4.1.2 is safe: the stored document layout is unchanged.
Rate sources
The provider set was reworked after CryptoCompare retired its free tier on 21 May 2026 and CoinGecko's keyless plan became unusable. Four connectors are new, and all four are keyless:
- CoinPaprika — one ranked bulk call per cycle plus a capped number of per-coin calls for coins outside the bulk range.
bulk_limitandmax_individual_requestsbound the request budget, and out-of-range coins are excluded at startup with a warning instead of burning quota every cycle - CoinLore — the whole coin set in a single multi-ID request. Numeric CoinLore IDs are reassigned across listings, so a response whose symbol does not match the configured ID is rejected at runtime rather than trusted
- Binance — public spot market data from an exchange rather than an aggregator, which is genuine source independence from the four aggregators that partly share upstream data. Binance has no direct USD pairs, so rates are requested against a configurable
quote_asset(USDTby default) and served as USD; a depeg splits the affected rates into their own divergence group instead of being hidden by a mapping. HTTP 451 geo-blocking disables the connector and alerts once instead of failing every cycle - ExchangeRate-API — keyless fiat rates, 166 currencies, daily updates
Existing connectors:
- CryptoCompare is deprecated and disabled by default, removed from
priorities, and scheduled for removal in the next major release. An API key is mandatory when it is enabled - CoinGecko is disabled by default and now requires a free Demo key, because the keyless public plan throttles to 5–15 calls per minute and rate-limits unpredictably
Five sources are now enabled by default with no credential at all, which is what makes the shipped config.default.jsonc a working configuration: three keyless crypto sources and two keyless fiat sources means minSources: 2 is satisfiable out of the box.
Important
Keyless access is not the same as permission to republish. CoinPaprika and ExchangeRate-API both restrict redistribution by a public or commercial instance. Read source terms and redistribution before serving these rates onwards.
Rate calculation and API correctness
tickersindexes are date-ordered ({ base: 1, date: -1 },{ quote: 1, date: -1 },{ base: 1, quote: 1, date: -1 }), so/getHistorysorts are index-provided instead of blocking in-memory sorts. Measured on a 238 million document collection, the same pair-and-range query went from 22.8 s to 8 ms- Historical
coinfilters now use the documentedBASE/QUOTEorder. On 4.1.x the pair filter was inverted, socoin=ADM/USDmatched nothing at all. Deployments carrying a client-side reversal workaround must remove it minSourcesis measured against sources that are fresh for the requested lifetime, so a stale provider can no longer satisfy the source-count gate. The effective threshold ismin(minSources, coverage), which keeps a single-provider pair served while still reporting it in the startup warning- Triangulation rejects cross rates that round to zero or come out non-finite
/getand/getHistoryschemas are.strict(), so an unknown query parameter is a400rather than something silently ignored. Validation errors now return400instead of500- Coin symbols accepted from providers and from query filters are a wider Unicode-aware form, so every stored pair is addressable —
$CWIFis a real example — while typos inbase_coinsandmappingsstill fail at startup /statusreportsupdatingfrom actual update state rather than inferring it from a timestamp comparison, and concurrent refresh cycles are skipped rather than overlapped
Security
- Webhook URLs, API keys and passphrases are redacted from log output, log files and notification dispatch. This closes a real leak: an upstream error response put an API key into the log through the Axios error URL
- The
logsdirectory is created0o750and log files0o600, best-effort so a mounted volume owned by another user never blocks startup - Log file names no longer contain colons, which made them unopenable on some filesystems
- The container runs as the unprivileged
nodeuser, npm/pnpm/yarn are removed from the runtime image,apk upgradeapplies outstanding Alpine updates at build time, and a build-time guard fails the build if a production dependency ever ships a native binding config.jsoncis deliberately absent from the image, so no layer can carry a credentialx-powered-byis disabled, and configuration validation rejects unknown keys and case-mismatched values- Trivy scanning runs in CI, and the vulnerability policy is documented.
multerandjs-yamlare pinned throughoverrides, sopnpm auditandpnpm audit --prodboth come back clean
Platform and dependencies
- Node.js
>= 22.12.0, pnpm pinned to12.3.4viapackageManager - NestJS 10 → 12, Mongoose 8 → 9, Zod 3 → 4,
adamant-api2 → 3, chalk 4 → 6 - ESLint 8 → 10 with flat config, TypeScript 5 → 6, Jest 29 → 30, Prettier 3.9
pnpm-workspace.yamlcarries the install-script allow-list thatpnpm run deps:setuprelies on- MongoDB connection errors are logged rather than swallowed, and the bootstrap failure path exits non-zero with a sanitized message
Distribution
publish-docker.yml— release-driven multi-platform publication toghcr.io/adamant-im/currencyinfoforlinux/amd64andlinux/arm64, with OCI labels, an SBOM and a build provenance attestation. The job refuses to continue unless the release tag is an ancestor ofmaster, andlatestmoves only for the newest non-prereleasedocker-ci.yml— builds the production image on pull requests and smoke-tests it against the shippedconfig.default.jsonc, proving the default configuration starts and returns rates with no API key. It verifies OCI labels, confirms the image carries no configuration or secrets, and confirms the container runs as non-root. It never pushesdocker-compose.prod.yamlpulls the published image instead of building, mounts the config read-only, and pinsmongo:8.0. The development Compose file binds MongoDB to127.0.0.1instead of every interface
docker pull ghcr.io/adamant-im/currencyinfo:4.2.0Documentation and tests
- A documentation site at https://currencyinfo.docs.adamant.im: quick start, installation, architecture, rate calculation, history, notifications, operations, security, troubleshooting, upgrading, plus a full REST and configuration reference and a page per rate source documenting quota, identifier form, failure mode and redistribution terms
LICENSE(GPL-3.0) added as a file for the first time, along withCONTRIBUTING.mdandAGENTS.md- The test suite grew from 3 spec files to 28, covering every source connector, the merger and its strategies, the sources manager, configuration loading, migration and schema validation, the logger, the notifier, both request schemas, the exception filter, the validation pipe, the interceptor, the controller and the shared utilities.
pnpm testruns 266 tests
Links
- Documentation · Upgrade guide · Container image
- Full changelog: v4.1.2...v4.2.0