diff options
| author | Adam Soltys <[email protected]> | 2026-07-10 22:56:12 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-10 22:56:12 -0700 |
| commit | b9791efba00cd00837699fba92a8d27528968395 (patch) | |
| tree | 512a935a315227f572b2e53d7ee5f0d304475ac7 | |
| parent | 7d8cd769e6cf91dda70fdf06db72a0b03e263dd7 (diff) | |
| download | coinos-server-b9791efba00cd00837699fba92a8d27528968395.tar.gz coinos-server-b9791efba00cd00837699fba92a8d27528968395.zip | |
rates: source Iranian IRR/IRT from Wallex, decoupled from the tick loop
apiv2.nobitex.ir became unreachable from our egress, and it was fetched
inline on every binance tick, so it failed and logged on every tick.
Move the Iranian market-rate fetch onto its own 60s timer with a request
timeout, cache the last-good value (falling back to the fixer rate after
an hour), and throttle failure logging. Switch the source to Wallex
(api.wallex.ir BTCTMN), which is reachable.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | lib/rates.ts | 63 |
1 files changed, 44 insertions, 19 deletions
diff --git a/lib/rates.ts b/lib/rates.ts index 03a3dcea..21a075b3 100644 --- a/lib/rates.ts +++ b/lib/rates.ts @@ -8,6 +8,18 @@ import WebSocket from "ws"; export let rate; let last; let ws; + +// The Iranian market IRR is fetched on its own timer rather than on every binance +// tick — fetching inline hammered the source and, when it's unreachable (recurring +// geo-block / outage), failed + logged on every tick. We cache the last-good value +// and apply it in onmessage; the tick loop never blocks on it. +// Source: Wallex (api.wallex.ir) BTCTMN last price. We switched off Nobitex on +// 2026-07-10 after apiv2.nobitex.ir became unreachable from our egress (its +// Iranian IPs refuse foreign connections); Wallex's API is reachable and its +// BTCTMN cross-checks against BTCUSDT × USDTTMN to <0.1%. +let iranIrr = 0; +let iranIrrTime = 0; +let iranErrLogged = 0; const connect = async () => { if (ws && ws.readyState === 1 && Date.now() - last < 5000) return; if (ws) ws.terminate() && (await sleep(Math.round(Math.random() * 1000))); @@ -25,25 +37,13 @@ const connect = async () => { rates[symbol] = msg.c * fx[symbol]; }); - try { - // Nobitex is the real Iranian market rate (fixer's IRR is the - // official peg, which undervalues by ~20%). Its API quotes in RIAL - // despite the IRT pair name, so IRR = lastTradePrice and Toman = - // Rial / 10. (Host moved from api. to apiv2.; the old one is now - // NXDOMAIN, which silently blanked both currencies.) - const irr = Number( - ( - (await got( - "https://apiv2.nobitex.ir/v2/orderbook/BTCIRT", - ).json()) as any - ).lastTradePrice, - ); - if (irr > 0) { - rates.IRR = irr; - rates.IRT = irr / 10; - } - } catch (e) { - err("nobitex IRR/IRT rate fetch failed", e.message); + // Apply the Iranian market IRR cached by updateIranRate() below (Wallex). + // iranIrr is stored in Rial, so IRR = iranIrr and Toman = Rial / 10. If the + // source has been unreachable for over an hour, we leave the fixer official + // IRR set in the fx loop above rather than serving an ever-staler market rate. + if (iranIrr > 0 && Date.now() - iranIrrTime < 60 * 60 * 1000) { + rates.IRR = iranIrr; + rates.IRT = iranIrr / 10; } rate = msg.c; @@ -62,6 +62,31 @@ const connect = async () => { return ws; }; +// Fetch the Iranian market rate from Wallex on its own 60s timer, decoupled from +// the binance tick loop. Wallex quotes BTCTMN in Toman, so IRR (Rial) = price*10. +// Caches the last-good value; on failure (recurring geo-block / outage) throttles +// error logging to once per 5 min instead of spamming on every attempt. A 5s +// request timeout keeps a hung endpoint from piling up. +const updateIranRate = async () => { + try { + const data = (await got("https://api.wallex.ir/v1/markets", { + timeout: { request: 5000 }, + }).json()) as any; + const tmn = Number(data?.result?.symbols?.BTCTMN?.stats?.lastPrice); + if (tmn > 0) { + iranIrr = tmn * 10; // Toman -> Rial + iranIrrTime = Date.now(); + } + } catch (e) { + if (Date.now() - iranErrLogged > 5 * 60 * 1000) { + err("Iran IRR/IRT rate fetch failed (wallex)", e.message); + iranErrLogged = Date.now(); + } + } + setTimeout(updateIranRate, 60000); +}; +updateIranRate(); + export const getFx = async () => { connect(); |
