Skip to content

Conversation

@samholmes
Copy link
Contributor

@samholmes samholmes commented Jan 17, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

Modernizes logging and configuration, expands transport support, and simplifies plugin coverage.

  • Logging: Replace ad-hoc console logging with Pino (src/util/logger.ts), add scoped child loggers, and document usage/output (docs/logging.md). Refactor hub, blockbook, evmRpc, and index to emit structured JSON logs with connection/IP and plugin metadata.
  • Service key templating: Introduce serviceKeys cleaner/matcher and URL templating (authenticateUrl, replaceUrlParams, serviceKeysFromUrl) to inject API keys by host/subdomain; update scan adapters to pull keys per-URL.
  • Transports: Add WebSocket support to EVM RPC via viem (auto-select http/ws per URL); improve error handling and tracing; add address prefix helpers for concise logs.
  • Plugins: Use templated NowNodes URLs for Blockbook chains; reduce EVM plugin list to core networks (BSC, Botanix, Ethereum, Optimism, Polygon, zkSync) plus fakePlugin.
  • Config/Tests: Remove nowNodesApiKey in favor of serviceKeys; update serverConfig, Jest setup, and tests accordingly; add pino dependency.

Written by Cursor Bugbot for commit b270b9f. This will update automatically on new commits. Configure here.


cursor[bot]

This comment was marked as outdated.

- Use Pino for performant logging
- Use Pino's .child() pattern for efficient scoped logging
- Add pino-pretty support for human-readable development output
- Disable logging in tests via NODE_ENV check
- Automatically rename conflicting fields (time → time_, scope → scope_)
Remove the following plugins to reduce load:
- abstract, amoy, arbitrum, avalanche, base
- bobevm, celo, ethereumclassic, ethereumpow, fantom
- filecoinfevm, filecoinfevmcalibration, holesky, hyperevm
- pulsechain, rsk, sepolia, sonic
Add serviceKeysFromUrl utility that matches API keys to URLs by hostname
with subdomain fallback support (e.g., api.example.com → example.com).
Keys can be configured with or without ports.

- Add src/util/serviceKeys.ts with ServiceKeys type and matching logic
- Add src/util/replaceUrlParams.ts for {{param}} placeholder substitution
- Add src/util/authenticateUrl.ts to combine key lookup with URL templating
- Add comprehensive tests for serviceKeysFromUrl

Refactor RPC and scan adapter configuration:
- Switch to authenticated RPC endpoints using {{apiKey}} templates
- Make scanAdapters required in EvmRpcOptions
- Update EtherscanV1/V2 adapters to use serviceKeysFromUrl
- Fix pickRandom return type to T | undefined for empty arrays
- Improve error handling when no URLs or API keys are configured
This reduces complexity and re-uses the new infrastructure.

The only trade-off is to remove the logging of the URL without the
key, which we can accept since these logs should be treated as
company sensitive.
This was referenced Jan 21, 2026
Copy link
Contributor

@swansontec swansontec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a few small things.

.then(() => initBlockConnection())
.catch(err => {
console.error('Failed to re-initialize block connection:', err)
logger.error({ err }, 'Failed to re-initialize block connection')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below we did ${String(error}. Here we log the object. Should we standardize on one of the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, standardize on logging the object. We get full stack traces this way.

pluginErrorCounter.inc({ pluginId, url: logUrl })

logger.warn('WebSocket error:', error)
logger.warn(`WebSocket error: ${String(error)}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above we did { err }. Here we log the stringified version. Should we standardize on one of the other?

pluginLogger.error('message')

// Pass an object with extra fields:
pluginLogger.info({ ip: '1.2.3.4' }, 'connected')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You pass an object with a msg field in a ton of places, but your own docs suggest that this is the wrong approach. Should you be passing the msg field as a second string parameter? If so, that applies to a bunch of places.

logger.warn({ host, msg: 'No API key found' })
if (url == null) {
logger.error({ msg: 'No URLs for EtherscanV2ScanAdapter provided' })
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here?

const socketReady = new Promise<void>(resolve => {
ws.on('open', () => {
pluginConnectionCounter.inc({ pluginId, url: logUrl })
pluginConnectionCounter.inc({ pluginId, url: url })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url: url could be just url. There are a bunch of other places where this happens in this same commit.

"Standardize on `{ msg: ... }` and `{ err }`" - Pass msg as second string parameter to logger methods instead of in the object. Use `{ err: error }` for error logging instead of String(error). Also removes debug `{ scope: 'foo' }`, uses shorthand `url` instead of `url: url`, and returns true when no URLs available (fail-safe).
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

const socketReady = new Promise<void>(resolve => {
ws.on('open', () => {
pluginConnectionCounter.inc({ pluginId, url: logUrl })
pluginConnectionCounter.inc({ pluginId, url })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API keys exposed in Prometheus metric labels

High Severity

The url parameter now contains the actual API key (substituted by authenticateUrl in allPlugins.ts) and is used directly in Prometheus metric labels via pluginConnectionCounter.inc({ pluginId, url }) and similar calls. The old code maintained a separate logUrl with the template placeholder {nowNodesApiKey} for metrics, while using connectionUrl with the actual key only for connections. This change exposes API keys to anyone with access to the Prometheus metrics endpoint.

Additional Locations (2)

Fix in Cursor Fix in Web

@samholmes
Copy link
Contributor Author

Closing this PR since we've dropped the commit that is the main reason for it (the WSS url support).

All the other commits will be in #13

@samholmes samholmes closed this Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants