Skip to content

Support all coingecko coins in marketstats and v4 fiatrates endpoints#4110

Open
msalcala11 wants to merge 1 commit intobitpay:masterfrom
msalcala11:more-rates
Open

Support all coingecko coins in marketstats and v4 fiatrates endpoints#4110
msalcala11 wants to merge 1 commit intobitpay:masterfrom
msalcala11:more-rates

Conversation

@msalcala11
Copy link
Contributor

Description

This PR expands CoinGecko support for /v1/marketstats/:code/ and /v4/fiatrates/:code/ so that rates and stats can also be returned for non-default coins. It adds symbol resolution via CoinGecko /search with /coins/list fallback, chain-aware disambiguation, and optional tokenAddress contract lookup for unambiguous token resolution.

Changelog

  • Added dynamic coin symbol resolution for marketstats/fiatrates (/v3/search first, /v3/coins/list fallback).
  • Added optional chain handling for symbol disambiguation.
  • Added optional tokenAddress lookup using CoinGecko contract endpoint when paired with a valid chain.
  • Preserved default-coin DB cache behavior.
  • Non-default coin requests bypass DB cache but are still cached at the edge by Cloudflare.
  • Refactored CoinGecko caching/fetching logic to use shared helper flows.
  • Expanded CoinGecko integration test coverage for search fallback, chain behavior, tokenAddress behavior, and cache expectations.

Testing Notes

Manual URL response checks:

Default market stats response (default DB-cached set): http://localhost:3232/bws/api/v1/marketstats/USD
Default fiat rates response (default DB-cached set): http://localhost:3232/bws/api/v4/fiatrates/USD
Non-default symbol resolution via search: http://localhost:3232/bws/api/v4/fiatrates/USD?coin=WBTC
/coins/list fallback case: http://localhost:3232/bws/api/v4/fiatrates/USD?coin=ABC
Token-address resolution for market stats: http://localhost:3232/bws/api/v1/marketstats/USD?coin=USDC.e&chain=arb&tokenAddress=0xff970a61a04b1ca14834a43f5de4533ebddb5cc8
Token-address resolution for fiat rates: http://localhost:3232/bws/api/v4/fiatrates/USD?coin=USDC.e&chain=arb&tokenAddress=0xff970a61a04b1ca14834a43f5de4533ebddb5cc8
Missing chain with tokenAddress should error: http://localhost:3232/bws/api/v1/marketstats/USD?coin=USDC.e&tokenAddress=0xff970a61a04b1ca14834a43f5de4533ebddb5cc8
Unknown tokenAddress on valid chain should return Unsupported tokenAddress: http://localhost:3232/bws/api/v1/marketstats/USD?coin=USDC.e&chain=arb&tokenAddress=0x0000000000000000000000000000000000000001

Cache behavior to verify:

  • Default coin requests should use DB cache keys (cgMarketStats:*, cgFiatRates:*).
  • Explicit non-default coin requests should bypass DB cache.
  • Those non-default responses are still cached by Cloudflare because these endpoints are served with public cache headers.


let ids: string[];
let secondary: string | undefined;
for (const id of candidates.slice(0, 10)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

each iteration makes a blocking /v3/coins/{id} request. if the goal is to get the first instance for secondary consider using Promise.all/Promise.allSettled here and picking the first match from the results

API_KEY: config.coinGecko.apiKey,
};
if (httpStatusCode === 429 || coinGeckoErrorCode === 429) {
const rateLimitErr: any = new Error('coinGecko rate limit');
Copy link
Contributor

Choose a reason for hiding this comment

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

i think its worth it to keep the rate limit logging

logger.warn('CoinGecko rate limit: %o', { url, httpStatusCode, coinGeckoErrorCode, body });

}
const hasCoinGeckoError = !!(status?.error_code || status?.error_message);
if ((httpStatusCode && httpStatusCode >= 400) || hasCoinGeckoError) {
const cgErr: any = new Error(status?.error_message || body?.status || 'coinGecko error');
Copy link
Contributor

Choose a reason for hiding this comment

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

same thing here with maintaining original logging. and do we want to keep the body field in the error like before?

cgErr.body = body;

try {
chainParam = getQueryString(req.query, 'chain');
} catch {
chainParam = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a reason to not throw an error here?

try {
chainParam = getQueryString(req.query, 'chain');
} catch {
chainParam = undefined;
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. undefined might lead to silent errors

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.

2 participants