Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ CONFIG_CHAIN=testnet
RPC_URL_MAINNET=https://rpc.citreascan.com
RPC_URL_TESTNET=https://rpc.testnet.citreascan.com

# CoinGecko: set EITHER a base URL pointing at a fronting pricing proxy (which
# injects the upstream key itself, recommended for in-cluster deployments) OR a
# direct Pro key. Setting only COINGECKO_BASE_URL is the cleanest setup; setting
# only COINGECKO_API_KEY routes through pro-api.coingecko.com directly.
# COINGECKO_BASE_URL=http://pricing-proxy:8080/coingecko
COINGECKO_API_KEY=[API-KEY]

TELEGRAM_BOT_TOKEN=[API-KEY]
Expand Down
32 changes: 27 additions & 5 deletions api.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ dotenv.config();
const isMainnet = process.env.CONFIG_CHAIN === 'mainnet';
if (isMainnet && process.env.RPC_URL_MAINNET === undefined) throw new Error('RPC_URL_MAINNET not available');
if (!isMainnet && process.env.RPC_URL_TESTNET === undefined) throw new Error('RPC_URL_TESTNET not available');
if (process.env.COINGECKO_API_KEY === undefined) throw new Error('COINGECKO_API_KEY not available');
// Either a key for direct Pro access OR a base URL for a fronting pricing proxy
// must be set; otherwise the upstream CoinGecko calls are anonymous and fail
// under load.
if (!process.env.COINGECKO_API_KEY && !process.env.COINGECKO_BASE_URL) {
throw new Error('COINGECKO_API_KEY or COINGECKO_BASE_URL must be set');
}

// Config type
export type ConfigType = {
app: string;
indexer: string;
indexerFallback: string;
coingeckoApiKey: string;
coingeckoApiKey: string | undefined;
coingeckoBaseUrl: string | undefined;
chain: Chain;
network: {
mainnet: string;
Expand All @@ -41,7 +47,8 @@ export const CONFIG: ConfigType = {
app: process.env.CONFIG_APP_URL,
indexer: process.env.CONFIG_INDEXER_URL,
indexerFallback: process.env.CONFIG_INDEXER_FALLBACK_URL,
coingeckoApiKey: process.env.COINGECKO_API_KEY,
coingeckoApiKey: process.env.COINGECKO_API_KEY || undefined,
coingeckoBaseUrl: process.env.COINGECKO_BASE_URL || undefined,
chain: isMainnet ? mainnet : testnet,
network: {
mainnet: process.env.RPC_URL_MAINNET,
Expand Down Expand Up @@ -76,6 +83,10 @@ const SENSITIVE_KEYS = new Set<string>([
'twitter.accessSecret',
]);

// `coingeckoBaseUrl` is intentionally NOT redacted — it is a non-secret pointer
// (typically the in-cluster pricing proxy origin) and useful at startup for
// confirming routing.

function redactConfig<T>(config: T): T {
return walkRedact(config, '') as T;
}
Expand Down Expand Up @@ -115,10 +126,21 @@ export const VIEM_CONFIG = createPublicClient({
});

// COINGECKO CLIENT
//
// Resolution priority:
// 1. COINGECKO_BASE_URL set → trust the caller (typically a pricing proxy that
// injects the upstream key itself); send no auth.
// 2. COINGECKO_API_KEY set → Pro tier: pro-api.coingecko.com with the
// `x-cg-pro-api-key` header. The earlier query-string form is supported by
// CoinGecko but cache-poisons proxies that key on the URL.
export const COINGECKO_CLIENT = (query: string) => {
const hasParams = query.includes('?');
if (CONFIG.coingeckoBaseUrl) {
return fetch(`${CONFIG.coingeckoBaseUrl}${query}`);
}
const uri: string = `https://pro-api.coingecko.com${query}`;
return fetch(`${uri}${hasParams ? '&' : '?'}x_cg_pro_api_key=${CONFIG.coingeckoApiKey}`);
return fetch(uri, {
headers: { 'x-cg-pro-api-key': CONFIG.coingeckoApiKey ?? '' },
});
};

export const PROTOCOL_STABLECOIN_SYMBOL = 'JUSD';
Expand Down
Loading