feat: add CoinGecko API key support with env-based attribution and docs updates
This commit is contained in:
11
.env.example
11
.env.example
@@ -1,3 +1,12 @@
|
|||||||
# No API keys are required for the default setup.
|
# Optional: CoinGecko API key (server-side only).
|
||||||
|
# Preferred:
|
||||||
|
# COINGECKO_PRO_API_KEY=
|
||||||
|
# or
|
||||||
|
# COINGECKO_DEMO_API_KEY=
|
||||||
|
#
|
||||||
|
# Fallback generic option:
|
||||||
|
# COINGECKO_API_KEY=
|
||||||
|
# COINGECKO_API_KEY_TYPE=demo # demo | pro
|
||||||
|
#
|
||||||
# Optional: override the internal API route base when deploying behind a proxy.
|
# Optional: override the internal API route base when deploying behind a proxy.
|
||||||
# NEXT_PUBLIC_API_BASE_URL=
|
# NEXT_PUBLIC_API_BASE_URL=
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -118,7 +118,22 @@ npm run sync:crypto-icons
|
|||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
No API keys are required.
|
CoinGecko key is optional but recommended to reduce rate-limit issues.
|
||||||
|
|
||||||
|
Preferred server-side variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COINGECKO_PRO_API_KEY=
|
||||||
|
# or
|
||||||
|
COINGECKO_DEMO_API_KEY=
|
||||||
|
```
|
||||||
|
|
||||||
|
Fallback generic option:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COINGECKO_API_KEY=
|
||||||
|
COINGECKO_API_KEY_TYPE=demo # demo | pro
|
||||||
|
```
|
||||||
|
|
||||||
Optional variable (only if you want to call API routes through a custom base URL):
|
Optional variable (only if you want to call API routes through a custom base URL):
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,25 @@ export function ConverterCard({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-border/70 bg-background/50"
|
className="border-border/70 bg-background/50"
|
||||||
>
|
>
|
||||||
Rates: {data.sources.fiat} • {data.sources.crypto}
|
<span className="text-muted-foreground">Fiat rates by</span>
|
||||||
|
<a
|
||||||
|
href="https://frankfurter.dev/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="ml-1 text-foreground transition-colors hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
{data.sources.fiat}
|
||||||
|
</a>
|
||||||
|
<span className="mx-1 text-muted-foreground">•</span>
|
||||||
|
<span className="text-muted-foreground">Price data by</span>
|
||||||
|
<a
|
||||||
|
href="https://www.coingecko.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="ml-1 text-foreground transition-colors hover:text-cyan-100"
|
||||||
|
>
|
||||||
|
{data.sources.crypto}
|
||||||
|
</a>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className="pr-1 text-base/7 sm:text-sm">
|
<CardDescription className="pr-1 text-base/7 sm:text-sm">
|
||||||
|
|||||||
@@ -7,6 +7,38 @@ export interface CryptoRateResult {
|
|||||||
|
|
||||||
const COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3";
|
const COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3";
|
||||||
|
|
||||||
|
function buildCoinGeckoHeaders(): HeadersInit {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
accept: "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const proApiKey = process.env.COINGECKO_PRO_API_KEY?.trim();
|
||||||
|
const demoApiKey = process.env.COINGECKO_DEMO_API_KEY?.trim();
|
||||||
|
const genericApiKey = process.env.COINGECKO_API_KEY?.trim();
|
||||||
|
const genericApiKeyType =
|
||||||
|
process.env.COINGECKO_API_KEY_TYPE?.trim().toLowerCase() ?? "demo";
|
||||||
|
|
||||||
|
if (proApiKey) {
|
||||||
|
headers["x-cg-pro-api-key"] = proApiKey;
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (demoApiKey) {
|
||||||
|
headers["x-cg-demo-api-key"] = demoApiKey;
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (genericApiKey) {
|
||||||
|
if (genericApiKeyType === "pro") {
|
||||||
|
headers["x-cg-pro-api-key"] = genericApiKey;
|
||||||
|
} else {
|
||||||
|
headers["x-cg-demo-api-key"] = genericApiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchCryptoData(): Promise<CryptoRateResult> {
|
export async function fetchCryptoData(): Promise<CryptoRateResult> {
|
||||||
const ids = CRYPTO_ASSETS.map((asset) => asset.providerId).filter(
|
const ids = CRYPTO_ASSETS.map((asset) => asset.providerId).filter(
|
||||||
(id): id is string => Boolean(id)
|
(id): id is string => Boolean(id)
|
||||||
@@ -18,9 +50,7 @@ export async function fetchCryptoData(): Promise<CryptoRateResult> {
|
|||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
next: { revalidate: 60 },
|
next: { revalidate: 60 },
|
||||||
headers: {
|
headers: buildCoinGeckoHeaders()
|
||||||
accept: "application/json"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user