From 6976c8b3e03061c94c64071149d2363437bbab20 Mon Sep 17 00:00:00 2001 From: def Date: Sun, 17 May 2026 21:17:35 +0000 Subject: [PATCH] Bump Monitor to v1.1.0 with dynamic version badge --- PROJECT_STATE.md | 12 ++ README.md | 11 + VERSION | 2 +- frontend/app.js | 23 +- frontend/brand.js | 44 ++++ frontend/favicon.png | Bin 0 -> 13854 bytes frontend/index.html | 66 +++++- frontend/styles.css | 471 +++++++++++++++++++++++++++-------------- oracle/assets.json | 17 +- oracle/fetch_prices.js | 151 ++++++------- package.json | 2 +- 11 files changed, 543 insertions(+), 256 deletions(-) create mode 100644 frontend/brand.js create mode 100644 frontend/favicon.png diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index a0577cc..4c8d117 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -1,3 +1,15 @@ +# PROJECT_STATE - Monitor + +## v1.1.0 - 2026-05-17 + +Current state: +- Monitor web UI is active for monitor.outsidethebox.top. +- Version badge is shown beside the main Monitor page title. +- OTB Oracle live quote panel is active. +- Billing-facing assets currently include USDC, ETH, ETHO, EGAZ, and ETI. +- Project version bumped to v1.1.0. +- Previous git version noted by user: v1.0.1. + ## Update - 2026-03-22 16:00 - Live frontend source of truth identified as /var/www/monitor. diff --git a/README.md b/README.md index f359341..4d8e6a0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +# Monitor v1.1.0 + +Build date: 2026-05-17 + +## v1.1.0 changes + +- Bumped monitor project from v1.0.1 to v1.1.0. +- Added visible version badge beside the Monitor page heading. +- Updated docs for the current OTB Oracle / monitor state. +- Current monitor page includes live OTB Oracle quote display and payment asset pricing context. + ## v1.0.1 - 2026-03-22 - Fixed live quote calculator so the entered CAD value no longer resets to the default during timed page refreshes. diff --git a/VERSION b/VERSION index b18d465..795460f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.0.1 +v1.1.0 diff --git a/frontend/app.js b/frontend/app.js index 08afabe..7b60a61 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -16,12 +16,12 @@ function applyTheme(theme) { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("theme", theme); - const toggle = document.getElementById("themeToggle"); + const toggle = document.getElementById("otbThemeToggle"); if (toggle) toggle.checked = (theme === "light"); } function toggleThemeFromCheckbox() { - const toggle = document.getElementById("themeToggle"); + const toggle = document.getElementById("otbThemeToggle"); const wantsLight = !!toggle?.checked; applyTheme(wantsLight ? "light" : "dark"); } @@ -31,7 +31,7 @@ function toggleThemeFromCheckbox() { applyTheme(saved); window.addEventListener("DOMContentLoaded", () => { - const toggle = document.getElementById("themeToggle"); + const toggle = document.getElementById("otbThemeToggle"); if (toggle) { toggle.addEventListener("change", toggleThemeFromCheckbox); toggle.checked = (document.documentElement.getAttribute("data-theme") === "light"); @@ -692,3 +692,20 @@ async function refresh(keepQuote = true) { setInterval(() => refresh(true), 10000); }); })(); + +async function loadMonitorVersionBadge() { + const badge = document.getElementById("monitor-version-badge"); + if (!badge) return; + + try { + const res = await fetch(`/VERSION?ts=${Date.now()}`, { cache: "no-store" }); + if (!res.ok) throw new Error(`VERSION fetch failed: ${res.status}`); + const version = (await res.text()).trim(); + badge.textContent = version || ""; + } catch (err) { + console.warn("Monitor version badge unavailable:", err); + badge.textContent = ""; + } +} + +document.addEventListener("DOMContentLoaded", loadMonitorVersionBadge); diff --git a/frontend/brand.js b/frontend/brand.js new file mode 100644 index 0000000..9b37576 --- /dev/null +++ b/frontend/brand.js @@ -0,0 +1,44 @@ +(function () { + const STORAGE_KEY = "otb_theme"; + const root = document.documentElement; + + function getPreferredTheme() { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved === "light" || saved === "dark") return saved; + return "dark"; + } + + function applyTheme(theme) { + root.setAttribute("data-theme", theme); + const toggle = document.getElementById("otbThemeToggle"); + if (toggle) { + toggle.checked = theme === "dark"; + } + } + + function saveTheme(theme) { + localStorage.setItem(STORAGE_KEY, theme); + } + + function initThemeToggle() { + const toggle = document.getElementById("otbThemeToggle"); + if (!toggle) return; + + toggle.addEventListener("change", function () { + const theme = toggle.checked ? "dark" : "light"; + applyTheme(theme); + saveTheme(theme); + }); + } + + function init() { + applyTheme(getPreferredTheme()); + initThemeToggle(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/frontend/favicon.png b/frontend/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0f6bf7cfbda9e5fff2d802dc372943d1630beb GIT binary patch literal 13854 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 Qvs+*?5{ly0q6UHg2R%YkvH$=8 literal 0 HcmV?d00001 diff --git a/frontend/index.html b/frontend/index.html index 7c161c8..6478dcf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,32 +7,76 @@ + +
-
Monitor
+
Monitor
7-day snapshot • rotating refresh
Loading…
- - -
- -
-
+
- +
+
+ All billing is calculated in 🇨🇦 CAD + + Crypto conversions use the OTB Oracle + + + Methods: + Credit Card (via Square), + e-Transfer, + and enabled crypto assets + +
+
+ + + diff --git a/frontend/styles.css b/frontend/styles.css index 524dec8..f027772 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -1,3 +1,305 @@ +/* ===== OTB shared branding ===== */ +html[data-theme="dark"]{ + --otb-bg:#081225; + --otb-bg2:#09172d; + --otb-text:#e8eefc; + --otb-muted:#aab6d6; + --otb-panel:rgba(18,24,37,.98); + --otb-panel-soft:rgba(18,24,37,.78); + --otb-line:rgba(255,255,255,.08); +} + +html[data-theme="light"]{ + --otb-bg:#f4f7fb; + --otb-bg2:#eef3f9; + --otb-text:#0f172a; + --otb-muted:#475569; + --otb-panel:rgba(255,255,255,.98); + --otb-panel-soft:rgba(255,255,255,.88); + --otb-line:rgba(15,23,42,.10); +} + +body{ + padding-bottom:56px; +} + +.site-container{ + max-width:1100px; + margin:0 auto; + padding:20px 18px 0 18px; +} + +.site-header{ + width:100%; +} + +.site-nav{ + display:flex; + align-items:center; + justify-content:space-between; + gap:18px; + padding:12px 0 22px 0; +} + +.site-brand{ + display:flex; + align-items:center; + gap:14px; + text-decoration:none; + color:inherit; +} + +.site-brand img{ + height:60px; + width:auto; + display:block; + object-fit:contain; + background:rgba(255,255,255,0.92); + padding:6px 12px; + border-radius:999px; + box-shadow:0 8px 24px rgba(0,0,0,0.35); +} + +.site-title{ + display:flex; + flex-direction:column; + line-height:1.1; +} + +.site-title strong{ + letter-spacing:.2px; + color:var(--otb-text); +} + +.site-title span{ + color:var(--otb-muted); + font-size:13px; + margin-top:2px; +} + +.site-nav-right{ + display:flex; + align-items:center; + gap:14px; + flex-wrap:wrap; + justify-content:flex-end; +} + +.site-navlinks{ + display:flex; + gap:12px; + flex-wrap:wrap; + justify-content:flex-end; + align-items:center; +} + +.site-navlinks > a, +.dropdown-toggle{ + text-decoration:none; + padding:8px 10px; + border-radius:12px; + color:var(--otb-muted); + border:1px solid transparent; +} + +.site-navlinks > a:hover, +.dropdown-toggle:hover{ + color:var(--otb-text); + border-color:var(--otb-line); + background:rgba(255,255,255,.03); +} + +.dropdown{ + position:relative; + display:inline-block; +} + +.dropdown-toggle{ + display:inline-block; + cursor:pointer; +} + +.dropdown-menu{ + position:absolute; + top:calc(100% + 8px); + right:0; + min-width:220px; + display:none; + padding:10px; + border-radius:14px; + background:var(--otb-panel); + border:1px solid var(--otb-line); + box-shadow:0 16px 40px rgba(0,0,0,.35); + z-index:9999; +} + +.dropdown:hover .dropdown-menu, +.dropdown:focus-within .dropdown-menu{ + display:block; +} + +.dropdown-menu a{ + display:block; + padding:9px 10px; + border-radius:10px; + color:var(--otb-muted); + text-decoration:none; + white-space:nowrap; + margin:0; +} + +.dropdown-menu a + a{ + margin-top:4px; +} + +.dropdown-menu a:hover{ + color:var(--otb-text); + background:rgba(255,255,255,.04); +} + +.otb-theme-switch{ + position:relative; + display:inline-block; + width:54px; + height:30px; + flex:0 0 auto; +} + +.otb-theme-switch input{ + opacity:0; + width:0; + height:0; +} + +.otb-theme-slider{ + position:absolute; + inset:0; + cursor:pointer; + background:rgba(255,255,255,.10); + border:1px solid var(--otb-line); + transition:.2s; + border-radius:999px; +} + +.otb-theme-slider:before{ + content:""; + position:absolute; + height:22px; + width:22px; + left:3px; + top:3px; + background:var(--otb-text); + transition:.2s; + border-radius:50%; +} + +.otb-theme-switch input:checked + .otb-theme-slider:before{ + transform:translateX(24px); +} + +.otb-statusbar{ + position:fixed; + left:0; + right:0; + bottom:0; + z-index:9999; + display:flex; + align-items:center; + justify-content:center; + min-height:42px; + padding:8px 14px; + background:var(--otb-panel); + border-top:1px solid var(--otb-line); + backdrop-filter:blur(8px); + box-shadow:0 -8px 24px rgba(0,0,0,.28); +} + +.otb-statusbar-inner{ + width:100%; + max-width:1100px; + display:flex; + gap:10px; + align-items:center; + justify-content:center; + flex-wrap:wrap; + text-align:center; + color:var(--otb-muted); + font-size:12px; + line-height:1.35; +} + +.otb-statusbar strong{ + color:var(--otb-text); + font-weight:700; +} + +.otb-statusbar a{ + color:#62e6b7; + text-decoration:none; + font-weight:600; +} + +.otb-statusbar a:hover{ + text-decoration:underline; +} + +.otb-dot{ + width:6px; + height:6px; + border-radius:999px; + display:inline-block; + background:rgba(255,255,255,.25); + flex:0 0 auto; +} + +@media (max-width: 900px){ + .site-nav{ + align-items:flex-start; + flex-direction:column; + } + + .site-nav-right{ + width:100%; + justify-content:space-between; + } + + .site-navlinks{ + justify-content:flex-start; + } + + .dropdown{ + width:100%; + } + + .dropdown-toggle{ + width:100%; + } + + .dropdown-menu{ + position:static; + right:auto; + top:auto; + min-width:100%; + margin-top:6px; + } + + .site-brand img{ + height:54px; + } +} + +@media (max-width: 700px){ + body{ + padding-bottom:72px; + } + + .otb-statusbar-inner{ + font-size:11px; + line-height:1.25; + } +} + + :root{ --bg:#0b0f19; --card:#101826; @@ -381,166 +683,11 @@ canvas.spark{ } } - -/* Quote calculator */ -.oracle-quote-wrap{ - display:flex; - flex-direction:column; - gap:10px; - border:1px solid rgba(255,255,255,.06); - border-radius:14px; - padding:12px; - background: rgba(0,0,0,.12); -} -[data-theme="light"] .oracle-quote-wrap{ - background: rgba(255,255,255,.55); - border:1px solid rgba(0,0,0,.06); -} - -.oracle-quote-title{ - font-size:13px; - letter-spacing:.08em; - text-transform:uppercase; - color:var(--muted); - font-weight:800; -} - -.oracle-quote-form{ - display:grid; - grid-template-columns:minmax(0,1fr) auto; - gap:10px; - align-items:end; -} - -.oracle-quote-field{ - display:flex; - flex-direction:column; - gap:6px; -} - -.oracle-quote-label{ - font-size:12px; - color:var(--muted); -} - -.oracle-quote-input{ - width:100%; - background: rgba(255,255,255,.04); - color: var(--text); - border:1px solid rgba(255,255,255,.10); - border-radius:10px; - padding:10px 12px; - outline:none; -} -[data-theme="light"] .oracle-quote-input{ - background: rgba(0,0,0,.03); - border:1px solid rgba(0,0,0,.10); -} - -.oracle-quote-actions{ - display:flex; - align-items:end; -} - -.oracle-quote-button{ - border:1px solid rgba(255,255,255,.10); - background: var(--toggle-on); - color:#fff; - border-radius:10px; - padding:10px 14px; - font-weight:800; - cursor:pointer; -} -.oracle-quote-button:hover{ - filter:brightness(1.06); -} - -.oracle-quote-results{ - display:flex; - flex-direction:column; - gap:8px; -} - -.oracle-quote-empty{ - font-size:12px; - color:var(--muted); -} - -.oracle-quote-meta{ - display:flex; - justify-content:space-between; - gap:10px; - font-size:12px; - color:var(--muted); -} - -.oracle-quote-list{ - display:flex; - flex-direction:column; - gap:8px; -} - -.oracle-quote-row{ - display:flex; - justify-content:space-between; - align-items:center; - gap:10px; - border:1px solid rgba(255,255,255,.06); - border-radius:12px; - padding:10px 12px; - background: rgba(255,255,255,.02); -} -[data-theme="light"] .oracle-quote-row{ - background: rgba(0,0,0,.02); - border:1px solid rgba(0,0,0,.06); -} - -.oracle-quote-row-left{ - min-width:0; -} - -.oracle-quote-asset{ - font-size:14px; - font-weight:800; -} - -.oracle-quote-sub{ - font-size:12px; - color:var(--muted); - margin-top:3px; -} - -.oracle-quote-row-right{ - display:flex; - flex-direction:column; - align-items:flex-end; - gap:6px; -} - -.oracle-quote-badges{ - display:flex; - gap:6px; - flex-wrap:wrap; - justify-content:flex-end; -} - -.oracle-quote-amount{ - font-size:15px; - font-weight:900; -} - -@media (max-width: 720px){ - .oracle-quote-form{ - grid-template-columns:1fr; - } - - .oracle-quote-row, - .oracle-quote-meta{ - flex-direction:column; - align-items:flex-start; - } - - .oracle-quote-row-right{ - align-items:flex-start; - } +.monitor-version-badge { + display: inline-block; + margin-left: 0.55rem; + font-size: 0.85rem; + font-weight: 600; + opacity: 0.72; + vertical-align: middle; } diff --git a/oracle/assets.json b/oracle/assets.json index bac41ff..5bdec6c 100644 --- a/oracle/assets.json +++ b/oracle/assets.json @@ -11,8 +11,9 @@ "decimals": 6, "billing_enabled": true, "quote_priority": 1, - "primary_source": "coingecko", - "fallback_source": "static_usd" + "primary_source": "coinpaprika", + "fallback_source": "coingecko", + "coinpaprika_id": "usdc-usdc" }, "ETH_ETH": { "symbol": "ETH", @@ -22,8 +23,9 @@ "decimals": 18, "billing_enabled": true, "quote_priority": 2, - "primary_source": "coingecko", - "fallback_source": "dexscreener" + "primary_source": "coinpaprika", + "fallback_source": "coingecko", + "coinpaprika_id": "eth-ethereum" }, "ETHO_ETHO": { "symbol": "ETHO", @@ -33,8 +35,9 @@ "decimals": 18, "billing_enabled": true, "quote_priority": 3, - "primary_source": "coingecko", - "fallback_source": "local" + "primary_source": "coinpaprika", + "fallback_source": "coingecko", + "coinpaprika_id": "etho-ethoprotocol" }, "EGAZ_ETICA": { "symbol": "EGAZ", @@ -42,7 +45,7 @@ "chain": "etica", "type": "native", "decimals": 18, - "billing_enabled": false, + "billing_enabled": true, "quote_priority": 4, "primary_source": "nonkyc", "fallback_source": "local" diff --git a/oracle/fetch_prices.js b/oracle/fetch_prices.js index 13b08f3..256b283 100644 --- a/oracle/fetch_prices.js +++ b/oracle/fetch_prices.js @@ -2,12 +2,13 @@ const { updateCachedPrice, loadCache } = require('./price_engine'); +const { fetchPair } = require('../backend/providers/klingex'); async function fetchJson(url) { const res = await fetch(url, { headers: { 'accept': 'application/json', - 'user-agent': 'otb-oracle/0.2' + 'user-agent': 'otb-oracle/0.3' } }); @@ -18,16 +19,10 @@ async function fetchJson(url) { return res.json(); } -async function fetchCoinGeckoSimplePrice() { - const ids = [ - 'usd-coin', - 'ethereum', - 'ether-1', - 'etica' - ].join(','); - +async function fetchCoinGeckoSimplePrice(ids) { + const joined = Array.isArray(ids) ? ids.join(',') : String(ids || ''); const url = - `https://api.coingecko.com/api/v3/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,cad`; + `https://api.coingecko.com/api/v3/simple/price?ids=${encodeURIComponent(joined)}&vs_currencies=usd,cad`; return fetchJson(url); } @@ -37,16 +32,23 @@ async function fetchCoinPaprikaTicker(coinId) { return fetchJson(url); } -function getCadPerUsd(coingeckoData) { - if (coingeckoData?.['usd-coin']?.cad && Number.isFinite(Number(coingeckoData['usd-coin'].cad))) { - return Number(coingeckoData['usd-coin'].cad); - } +function hasFinitePrice(value) { + return value !== null && value !== undefined && Number.isFinite(Number(value)) && Number(value) > 0; +} +function getCachedCadPerUsd() { try { const cache = loadCache(); - const cachedCad = cache?.assets?.USDC_ARB?.price_cad; - if (cachedCad && Number.isFinite(Number(cachedCad))) { - return Number(cachedCad); + const usdc = cache?.assets?.USDC_ARB || {}; + const usd = Number(usdc.price_usd); + const cad = Number(usdc.price_cad); + + if (hasFinitePrice(usd) && hasFinitePrice(cad)) { + return cad / usd; + } + + if (hasFinitePrice(cad)) { + return cad; } } catch (_) { } @@ -54,11 +56,7 @@ function getCadPerUsd(coingeckoData) { return 1.38; } -function hasFinitePrice(value) { - return value !== null && value !== undefined && Number.isFinite(Number(value)) && Number(value) > 0; -} - -function maybeUpdateFromCoinGecko(pairKey, cgKey, sourceData, now) { +function maybeUpdateFromCoinGecko(pairKey, sourceData, now, sourceStatus = 'primary') { if (!sourceData || !hasFinitePrice(sourceData.usd) || !hasFinitePrice(sourceData.cad)) { return false; } @@ -67,14 +65,14 @@ function maybeUpdateFromCoinGecko(pairKey, cgKey, sourceData, now) { price_usd: Number(sourceData.usd), price_cad: Number(sourceData.cad), source: 'coingecko', - source_status: 'primary', + source_status: sourceStatus, updated_at: now }); return true; } -function maybeUpdateFromCoinPaprika(pairKey, tickerData, cadPerUsd, now) { +function maybeUpdateFromCoinPaprika(pairKey, tickerData, cadPerUsd, now, sourceStatus = 'primary') { const usd = Number(tickerData?.quotes?.USD?.price); if (!hasFinitePrice(usd)) { @@ -87,74 +85,85 @@ function maybeUpdateFromCoinPaprika(pairKey, tickerData, cadPerUsd, now) { price_usd: usd, price_cad: cad, source: 'coinpaprika', - source_status: 'fallback', + source_status: sourceStatus, updated_at: now }); return true; } -async function main() { - const now = new Date().toISOString(); - const updatedPairs = []; - - let cgData = {}; +async function updateFromPaprika(pairKey, coinId, cadPerUsd, now, updatedPairs, sourceStatus = 'primary') { try { - cgData = await fetchCoinGeckoSimplePrice(); + const ticker = await fetchCoinPaprikaTicker(coinId); + if (maybeUpdateFromCoinPaprika(pairKey, ticker, cadPerUsd, now, sourceStatus)) { + updatedPairs.push(pairKey); + return true; + } } catch (err) { - console.error(`CoinGecko fetch failed: ${err.message}`); - cgData = {}; + console.error(`CoinPaprika ${pairKey} fetch failed: ${err.message}`); } - const cadPerUsd = getCadPerUsd(cgData); - - if (maybeUpdateFromCoinGecko('USDC_ARB', 'usd-coin', cgData['usd-coin'], now)) { - updatedPairs.push('USDC_ARB'); - } + return false; +} - if (maybeUpdateFromCoinGecko('ETH_ETH', 'ethereum', cgData['ethereum'], now)) { - updatedPairs.push('ETH_ETH'); - } +async function updateETHOFromKlingEx(now, updatedPairs, cadPerUsd) { + try { + const r = await fetchPair('ETHO-USDT'); + const usd = Number(r.price); - 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}`); + if (!hasFinitePrice(usd)) { + throw new Error(`KlingEx returned non-usable ETHO price: ${r.price}`); } - } 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'); + const cad = Number((usd * cadPerUsd).toFixed(8)); + + updateCachedPrice('ETHO_ETHO', { + price_usd: usd, + price_cad: cad, + source: r.source || 'klingex', + source_status: 'primary', + updated_at: now + }); + + updatedPairs.push('ETHO_ETHO'); + return true; + } catch (err) { + console.error(`KlingEx ETHO fetch failed: ${err.message}`); } + // Last-resort fallback only. try { - const papEgaz = await fetchCoinPaprikaTicker('egaz-egaz'); - if (maybeUpdateFromCoinPaprika('EGAZ_ETICA', papEgaz, cadPerUsd, now)) { - updatedPairs.push('EGAZ_ETICA'); + const cgData = await fetchCoinGeckoSimplePrice(['ether-1']); + if (maybeUpdateFromCoinGecko('ETHO_ETHO', cgData['ether-1'], now, 'fallback')) { + updatedPairs.push('ETHO_ETHO'); + return true; } + + console.error('CoinGecko ETHO fallback returned no usable ether-1 price'); } catch (err) { - console.error(`Coinpaprika EGAZ fetch failed: ${err.message}`); + console.error(`CoinGecko ETHO fallback failed: ${err.message}`); } + return false; +} + +async function main() { + const now = new Date().toISOString(); + const updatedPairs = []; + let cadPerUsd = getCachedCadPerUsd(); + + await updateFromPaprika('USDC_ARB', 'usdc-usd-coin', cadPerUsd, now, updatedPairs, 'primary'); + + cadPerUsd = getCachedCadPerUsd(); + + await updateFromPaprika('ETH_ETH', 'eth-ethereum', cadPerUsd, now, updatedPairs, 'primary'); + + // ETHO uses KlingEx primary; CoinGecko only as last fallback. + await updateETHOFromKlingEx(now, updatedPairs, cadPerUsd); + + await updateFromPaprika('ETI_ETICA', 'eti-etica', cadPerUsd, now, updatedPairs, 'fallback'); + await updateFromPaprika('EGAZ_ETICA', 'egaz-egaz', cadPerUsd, now, updatedPairs, 'fallback'); + console.log(JSON.stringify({ ok: true, fetched_at: now, diff --git a/package.json b/package.json index 23d36f7..78dc8e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monitor", - "version": "1.0.0", + "version": "1.1.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1"