|
|
|
@ -1,12 +1,13 @@ |
|
|
|
const { |
|
|
|
const { |
|
|
|
updateCachedPrice |
|
|
|
updateCachedPrice, |
|
|
|
|
|
|
|
loadCache |
|
|
|
} = require('./price_engine'); |
|
|
|
} = require('./price_engine'); |
|
|
|
|
|
|
|
|
|
|
|
async function fetchJson(url) { |
|
|
|
async function fetchJson(url) { |
|
|
|
const res = await fetch(url, { |
|
|
|
const res = await fetch(url, { |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'accept': 'application/json', |
|
|
|
'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); |
|
|
|
return fetchJson(url); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function main() { |
|
|
|
async function fetchCoinPaprikaTicker(coinId) { |
|
|
|
const data = await fetchCoinGeckoSimplePrice(); |
|
|
|
const url = `https://api.coinpaprika.com/v1/tickers/${encodeURIComponent(coinId)}`; |
|
|
|
const now = new Date().toISOString(); |
|
|
|
return fetchJson(url); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (data['usd-coin']) { |
|
|
|
function getCadPerUsd(coingeckoData) { |
|
|
|
updateCachedPrice('USDC_ARB', { |
|
|
|
if (coingeckoData?.['usd-coin']?.cad && Number.isFinite(Number(coingeckoData['usd-coin'].cad))) { |
|
|
|
price_usd: data['usd-coin'].usd ?? null, |
|
|
|
return Number(coingeckoData['usd-coin'].cad); |
|
|
|
price_cad: data['usd-coin'].cad ?? null, |
|
|
|
|
|
|
|
source: 'coingecko', |
|
|
|
|
|
|
|
source_status: 'primary', |
|
|
|
|
|
|
|
updated_at: now |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (data['ethereum']) { |
|
|
|
try { |
|
|
|
updateCachedPrice('ETH_ETH', { |
|
|
|
const cache = loadCache(); |
|
|
|
price_usd: data['ethereum'].usd ?? null, |
|
|
|
const cachedCad = cache?.assets?.USDC_ARB?.price_cad; |
|
|
|
price_cad: data['ethereum'].cad ?? null, |
|
|
|
if (cachedCad && Number.isFinite(Number(cachedCad))) { |
|
|
|
source: 'coingecko', |
|
|
|
return Number(cachedCad); |
|
|
|
source_status: 'primary', |
|
|
|
} |
|
|
|
updated_at: now |
|
|
|
} catch (_) { |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (data['ether-1']) { |
|
|
|
return 1.38; |
|
|
|
updateCachedPrice('ETHO_ETHO', { |
|
|
|
} |
|
|
|
price_usd: data['ether-1'].usd ?? null, |
|
|
|
|
|
|
|
price_cad: data['ether-1'].cad ?? null, |
|
|
|
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: 'coingecko', |
|
|
|
source_status: 'primary', |
|
|
|
source_status: 'primary', |
|
|
|
updated_at: now |
|
|
|
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']) { |
|
|
|
const cad = Number((usd * cadPerUsd).toFixed(8)); |
|
|
|
updateCachedPrice('ETI_ETICA', { |
|
|
|
|
|
|
|
price_usd: data['etica'].usd ?? null, |
|
|
|
updateCachedPrice(pairKey, { |
|
|
|
price_cad: data['etica'].cad ?? null, |
|
|
|
price_usd: usd, |
|
|
|
source: 'coingecko', |
|
|
|
price_cad: cad, |
|
|
|
source_status: 'primary', |
|
|
|
source: 'coinpaprika', |
|
|
|
|
|
|
|
source_status: 'fallback', |
|
|
|
updated_at: now |
|
|
|
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({ |
|
|
|
console.log(JSON.stringify({ |
|
|
|
ok: true, |
|
|
|
ok: true, |
|
|
|
fetched_at: now, |
|
|
|
fetched_at: now, |
|
|
|
updated_pairs: ['USDC_ARB', 'ETH_ETH', 'ETHO_ETHO', 'ETI_ETICA'] |
|
|
|
cad_per_usd: cadPerUsd, |
|
|
|
|
|
|
|
updated_pairs: updatedPairs |
|
|
|
}, null, 2)); |
|
|
|
}, null, 2)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
main().catch(err => { |
|
|
|
main().catch(err => { |
|
|
|
console.error(`fetch_prices.js failed: ${err.message}`); |
|
|
|
console.error(`fetch_prices.js failed: ${err.message}`); |
|
|
|
process.exit(1); |
|
|
|
process.exit(1); |
|
|
|
|