Skip to content

v4.2.0

Latest

Choose a tag to compare

@metalisk metalisk released this 09 Sep 17:04
b424a8a

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_limit and max_individual_requests bound 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 (USDT by 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

  • tickers indexes are date-ordered ({ base: 1, date: -1 }, { quote: 1, date: -1 }, { base: 1, quote: 1, date: -1 }), so /getHistory sorts 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 coin filters now use the documented BASE/QUOTE order. On 4.1.x the pair filter was inverted, so coin=ADM/USD matched nothing at all. Deployments carrying a client-side reversal workaround must remove it
  • minSources is 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 is min(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
  • /get and /getHistory schemas are .strict(), so an unknown query parameter is a 400 rather than something silently ignored. Validation errors now return 400 instead of 500
  • Coin symbols accepted from providers and from query filters are a wider Unicode-aware form, so every stored pair is addressable — $CWIF is a real example — while typos in base_coins and mappings still fail at startup
  • /status reports updating from 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 logs directory is created 0o750 and log files 0o600, 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 node user, npm/pnpm/yarn are removed from the runtime image, apk upgrade applies outstanding Alpine updates at build time, and a build-time guard fails the build if a production dependency ever ships a native binding
  • config.jsonc is deliberately absent from the image, so no layer can carry a credential
  • x-powered-by is disabled, and configuration validation rejects unknown keys and case-mismatched values
  • Trivy scanning runs in CI, and the vulnerability policy is documented. multer and js-yaml are pinned through overrides, so pnpm audit and pnpm audit --prod both come back clean

Platform and dependencies

  • Node.js >= 22.12.0, pnpm pinned to 12.3.4 via packageManager
  • NestJS 10 → 12, Mongoose 8 → 9, Zod 3 → 4, adamant-api 2 → 3, chalk 4 → 6
  • ESLint 8 → 10 with flat config, TypeScript 5 → 6, Jest 29 → 30, Prettier 3.9
  • pnpm-workspace.yaml carries the install-script allow-list that pnpm run deps:setup relies 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 to ghcr.io/adamant-im/currencyinfo for linux/amd64 and linux/arm64, with OCI labels, an SBOM and a build provenance attestation. The job refuses to continue unless the release tag is an ancestor of master, and latest moves only for the newest non-prerelease
  • docker-ci.yml — builds the production image on pull requests and smoke-tests it against the shipped config.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 pushes
  • docker-compose.prod.yaml pulls the published image instead of building, mounts the config read-only, and pins mongo:8.0. The development Compose file binds MongoDB to 127.0.0.1 instead of every interface
docker pull ghcr.io/adamant-im/currencyinfo:4.2.0

Documentation 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 with CONTRIBUTING.md and AGENTS.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 test runs 266 tests

Links