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
13 changes: 12 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ CONFIG_CHAIN=mainnet
RPC_URL_MAINNET=https://eth-mainnet.g.alchemy.com/v2/[API-KEY]
RPC_URL_POLYGON=https://polygon-mainnet.g.alchemy.com/v2/[API-KEY]

COINGECKO_API_KEY=[API-KEY]
# CoinGecko Configuration.
#
# COINGECKO_BASE_URL: required. The origin the api calls. Recommended is
# the in-cluster pricing-proxy (https://github.com/DFXswiss/pricing-proxy),
# which holds the upstream Pro key and serves a 60 s shared cache. Anything
# CoinGecko-compatible works (pro-api.coingecko.com, api.coingecko.com, …).
COINGECKO_BASE_URL=http://pricing-proxy:8080/coingecko
#
# COINGECKO_API_KEY: optional. If set, attached as `x-cg-pro-api-key` to
# every request. Leave unset when talking to the pricing-proxy (proxy injects
# its own key) or to the public host anonymously.
# COINGECKO_API_KEY=

TELEGRAM_BOT_TOKEN=[API-KEY]
TELEGRAM_GROUPS_JSON=telegram.groups.json
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
pull_request:
branches: [develop, main]
push:
branches: [develop, main]

jobs:
build:
name: Build & Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build
run: yarn build

- name: Lint
run: yarn lint
continue-on-error: true
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,45 @@ CONFIG_CHAIN=mainnet
RPC_URL_MAINNET=https://eth-mainnet.g.alchemy.com/v2/[API-KEY]
RPC_URL_POLYGON=https://polygon-mainnet.g.alchemy.com/v2/[API-KEY]

COINGECKO_API_KEY=[API-KEY]
# See "CoinGecko" section below.
COINGECKO_BASE_URL=http://pricing-proxy:8080/coingecko
# COINGECKO_API_KEY=

TELEGRAM_BOT_TOKEN=[API-KEY]
TELEGRAM_GROUPS_JSON=telegram.groups.json
TELEGRAM_IMAGES_DIR=./images
```

## CoinGecko

The api fetches token prices from a CoinGecko-compatible endpoint.
Configuration is two env vars:

| Var | Required | Purpose |
|---|---|---|
| `COINGECKO_BASE_URL` | yes | Origin the api calls. |
| `COINGECKO_API_KEY` | no | Attached as the `x-cg-pro-api-key` header on every request when set. |

The recommended deployment is the
[**pricing-proxy**](https://github.com/DFXswiss/pricing-proxy) — a small
caching reverse-proxy in front of CoinGecko Pro. It holds the upstream
key, serves a 60 s shared cache, validates upstream error envelopes, and
coalesces concurrent identical requests. When you use the proxy:

```env
COINGECKO_BASE_URL=http://pricing-proxy:8080/coingecko
# COINGECKO_API_KEY left unset — the proxy injects its own key
```

Without the proxy, talk to CoinGecko directly:

```env
COINGECKO_BASE_URL=https://pro-api.coingecko.com
COINGECKO_API_KEY=CG-xxxxxxxxxxxxxxxxxxxxxxxx
```

The api refuses to start without `COINGECKO_BASE_URL`.

## Running the app

```bash
Expand Down
30 changes: 24 additions & 6 deletions api.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ dotenv.config();
// Verify environment
if (process.env.RPC_URL_MAINNET === undefined) throw new Error('RPC_URL_MAINNET not available');
if (process.env.RPC_URL_POLYGON === undefined) throw new Error('RPC_URL_POLYGON not available');
if (process.env.COINGECKO_API_KEY === undefined) throw new Error('COINGECKO_API_KEY not available');
// COINGECKO_BASE_URL is the origin the api calls — typically the in-cluster
// pricing-proxy (https://github.com/DFXswiss/pricing-proxy), but any
// CoinGecko-compatible host works. COINGECKO_API_KEY is optional and is
// only attached as `x-cg-pro-api-key` on every request when set (proxy mode
// leaves it unset because the proxy injects its own key).
if (!process.env.COINGECKO_BASE_URL) {
throw new Error('COINGECKO_BASE_URL is not set');
}

// Config type
export type ConfigType = {
app: string;
indexer: string;
indexerFallback: string;
coingeckoApiKey: string;
coingeckoBaseUrl: string;
coingeckoApiKey: string | undefined;
chain: Chain;
network: {
mainnet: string;
Expand All @@ -42,7 +50,8 @@ export const CONFIG: ConfigType = {
app: process.env.CONFIG_APP_URL || 'https://app.deuro.com',
indexer: process.env.CONFIG_INDEXER_URL || 'https://ponder.deuro.com/',
indexerFallback: process.env.CONFIG_INDEXER_FALLBACK_URL || 'https://dev.ponder.deuro.com/',
coingeckoApiKey: process.env.COINGECKO_API_KEY,
coingeckoBaseUrl: process.env.COINGECKO_BASE_URL,
coingeckoApiKey: process.env.COINGECKO_API_KEY || undefined,
chain: process.env.CONFIG_CHAIN === 'polygon' ? polygon : mainnet, // @dev: default mainnet
network: {
mainnet: process.env.RPC_URL_MAINNET,
Expand Down Expand Up @@ -112,10 +121,19 @@ export const VIEM_CONFIG = createPublicClient({
});

// COINGECKO CLIENT
//
// Calls go to whatever `COINGECKO_BASE_URL` points at. When the optional
// `COINGECKO_API_KEY` is set, it is attached as the `x-cg-pro-api-key`
// header — orthogonal to the base URL, never a fallback. The recommended
// deployment is the in-cluster pricing-proxy
// (https://github.com/DFXswiss/pricing-proxy), which injects its own key
// and leaves COINGECKO_API_KEY unset on every consumer.
export const COINGECKO_CLIENT = (query: string) => {
const hasParams = query.includes('?');
const uri: string = `https://pro-api.coingecko.com${query}`;
return fetch(`${uri}${hasParams ? '&' : '?'}x_cg_pro_api_key=${CONFIG.coingeckoApiKey}`);
const headers: Record<string, string> = { accept: 'application/json' };
if (CONFIG.coingeckoApiKey) {
headers['x-cg-pro-api-key'] = CONFIG.coingeckoApiKey;
}
return fetch(`${CONFIG.coingeckoBaseUrl}${query}`, { headers });
};

// Contract addresses for the active chain
Expand Down
Loading