Browse Source

Add Coinpaprika fallback for oracle pricing

main
def 1 week ago
parent
commit
07ab7d65a3
  1. 146
      oracle/fetch_prices.js
  2. 12
      oracle/price_cache.json
  3. 9
      oracle/sources.json

146
oracle/fetch_prices.js

@ -1,12 +1,13 @@
const {
updateCachedPrice
updateCachedPrice,
loadCache
} = require('./price_engine');
async function fetchJson(url) {
const res = await fetch(url, {
headers: {
'accept': 'application/json',
'user-agent': 'otb-oracle/0.1'
'user-agent': 'otb-oracle/0.2'
}
});
@ -31,56 +32,137 @@ async function fetchCoinGeckoSimplePrice() {
return fetchJson(url);
}
async function main() {
const data = await fetchCoinGeckoSimplePrice();
const now = new Date().toISOString();
async function fetchCoinPaprikaTicker(coinId) {
const url = `https://api.coinpaprika.com/v1/tickers/${encodeURIComponent(coinId)}`;
return fetchJson(url);
}
if (data['usd-coin']) {
updateCachedPrice('USDC_ARB', {
price_usd: data['usd-coin'].usd ?? null,
price_cad: data['usd-coin'].cad ?? null,
source: 'coingecko',
source_status: 'primary',
updated_at: now
});
function getCadPerUsd(coingeckoData) {
if (coingeckoData?.['usd-coin']?.cad && Number.isFinite(Number(coingeckoData['usd-coin'].cad))) {
return Number(coingeckoData['usd-coin'].cad);
}
if (data['ethereum']) {
updateCachedPrice('ETH_ETH', {
price_usd: data['ethereum'].usd ?? null,
price_cad: data['ethereum'].cad ?? null,
source: 'coingecko',
source_status: 'primary',
updated_at: now
});
try {
const cache = loadCache();
const cachedCad = cache?.assets?.USDC_ARB?.price_cad;
if (cachedCad && Number.isFinite(Number(cachedCad))) {
return Number(cachedCad);
}
} catch (_) {
}
if (data['ether-1']) {
updateCachedPrice('ETHO_ETHO', {
price_usd: data['ether-1'].usd ?? null,
price_cad: data['ether-1'].cad ?? null,
return 1.38;
}
function hasFinitePrice(value) {
return value !== null && value !== undefined && Number.isFinite(Number(value)) && Number(value) > 0;
}
function maybeUpdateFromCoinGecko(pairKey, cgKey, sourceData, now) {
if (!sourceData || !hasFinitePrice(sourceData.usd) || !hasFinitePrice(sourceData.cad)) {
return false;
}
updateCachedPrice(pairKey, {
price_usd: Number(sourceData.usd),
price_cad: Number(sourceData.cad),
source: 'coingecko',
source_status: 'primary',
updated_at: now
});
return true;
}
function maybeUpdateFromCoinPaprika(pairKey, tickerData, cadPerUsd, now) {
const usd = Number(tickerData?.quotes?.USD?.price);
if (!hasFinitePrice(usd)) {
return false;
}
if (data['etica']) {
updateCachedPrice('ETI_ETICA', {
price_usd: data['etica'].usd ?? null,
price_cad: data['etica'].cad ?? null,
source: 'coingecko',
source_status: 'primary',
const cad = Number((usd * cadPerUsd).toFixed(8));
updateCachedPrice(pairKey, {
price_usd: usd,
price_cad: cad,
source: 'coinpaprika',
source_status: 'fallback',
updated_at: now
});
return true;
}
async function main() {
const now = new Date().toISOString();
const updatedPairs = [];
let cgData = {};
try {
cgData = await fetchCoinGeckoSimplePrice();
} catch (err) {
console.error(`CoinGecko fetch failed: ${err.message}`);
cgData = {};
}
const cadPerUsd = getCadPerUsd(cgData);
if (maybeUpdateFromCoinGecko('USDC_ARB', 'usd-coin', cgData['usd-coin'], now)) {
updatedPairs.push('USDC_ARB');
}
if (maybeUpdateFromCoinGecko('ETH_ETH', 'ethereum', cgData['ethereum'], now)) {
updatedPairs.push('ETH_ETH');
}
let ethoUpdated = maybeUpdateFromCoinGecko('ETHO_ETHO', 'ether-1', cgData['ether-1'], now);
if (!ethoUpdated) {
try {
const pap = await fetchCoinPaprikaTicker('etho-ethoprotocol');
if (maybeUpdateFromCoinPaprika('ETHO_ETHO', pap, cadPerUsd, now)) {
updatedPairs.push('ETHO_ETHO');
ethoUpdated = true;
}
} catch (err) {
console.error(`Coinpaprika ETHO fallback failed: ${err.message}`);
}
} else {
updatedPairs.push('ETHO_ETHO');
}
let etiUpdated = maybeUpdateFromCoinGecko('ETI_ETICA', 'etica', cgData['etica'], now);
if (!etiUpdated) {
try {
const pap = await fetchCoinPaprikaTicker('eti-etica');
if (maybeUpdateFromCoinPaprika('ETI_ETICA', pap, cadPerUsd, now)) {
updatedPairs.push('ETI_ETICA');
etiUpdated = true;
}
} catch (err) {
console.error(`Coinpaprika ETI fallback failed: ${err.message}`);
}
} else {
updatedPairs.push('ETI_ETICA');
}
try {
const papEgaz = await fetchCoinPaprikaTicker('egaz-egaz');
if (maybeUpdateFromCoinPaprika('EGAZ_ETICA', papEgaz, cadPerUsd, now)) {
updatedPairs.push('EGAZ_ETICA');
}
} catch (err) {
console.error(`Coinpaprika EGAZ fetch failed: ${err.message}`);
}
console.log(JSON.stringify({
ok: true,
fetched_at: now,
updated_pairs: ['USDC_ARB', 'ETH_ETH', 'ETHO_ETHO', 'ETI_ETICA']
cad_per_usd: cadPerUsd,
updated_pairs: updatedPairs
}, null, 2));
}
main().catch(err => {
console.error(`fetch_prices.js failed: ${err.message}`);
process.exit(1);

12
oracle/price_cache.json

@ -1,6 +1,6 @@
{
"version": "0.1",
"updated_at": "2026-03-15T00:11:11.634Z",
"updated_at": "2026-03-15T00:19:19.555Z",
"status": "fresh",
"assets": {
"USDC_ARB": {
@ -28,11 +28,11 @@
"ETHO_ETHO": {
"symbol": "ETHO",
"chain": "etho",
"price_usd": 0.00525255,
"price_cad": 0.00714931,
"source": "coingecko",
"source_status": "primary",
"updated_at": "2026-03-15T00:11:11.630Z",
"price_usd": 0.005678438337985232,
"price_cad": 0.00783624,
"source": "coinpaprika",
"source_status": "fallback",
"updated_at": "2026-03-15T00:19:18.791Z",
"age_seconds": 0,
"stale": false
},

9
oracle/sources.json

@ -1,5 +1,5 @@
{
"version": "0.1",
"version": "0.2",
"updated": "2026-03-14",
"sources": {
@ -11,6 +11,13 @@
"rate_limit_seconds": 60
},
"coinpaprika": {
"type": "api",
"url": "https://api.coinpaprika.com/v1/tickers",
"description": "Fallback market price source by coin ID",
"rate_limit_seconds": 60
},
"dexscreener": {
"type": "api",
"url": "https://api.dexscreener.com/latest/dex/tokens",

Loading…
Cancel
Save