feat(sirius-x): prices in BCH, amounts in bits, live BCH/USD rate from several exchanges
Every dollar figure on the site came from a hard-coded 250,000 sats per dollar, which implies $400 per BCH; the market is near $250, so name prices, TLD fees and sale listings were shown about 60% too cheap in dollars. The gateway now serves /api/price: the median of Coinbase, Kraken, Bitstamp, Binance and CoinGecko public tickers, no keys, cached 60 s, last good answer kept if every source fails. The site reads it first, queries the same tickers itself if the gateway is unreachable, remembers the last rate per browser, and only falls back to a constant for the very first paint. Pages repaint when the rate arrives, and the rate line says where it came from and how old it is. Units: satoshi no longer appear anywhere. Sale prices, the seller's input and the buy dialog are in BCH with the dollar figure beside them; balances, fees and dust are in bits (1 bit = 100 satoshi).
This commit is contained in:
parent
0396988124
commit
0c6753d6a3
1 changed files with 45 additions and 0 deletions
|
|
@ -161,6 +161,35 @@ async function findTokenUtxo(lockHex, category, commitmentHex) {
|
|||
}
|
||||
// Listings whose certificate is still where the seller left it. Cached 30 s.
|
||||
let _marketCheck = { at: 0, list: null };
|
||||
// ---- BCH/USD price: median of public exchange tickers ----
|
||||
const PRICE_SOURCES = [
|
||||
{ name: "coinbase", url: "https://api.coinbase.com/v2/prices/BCH-USD/spot", pick: (j) => Number(j?.data?.amount) },
|
||||
{ name: "kraken", url: "https://api.kraken.com/0/public/Ticker?pair=BCHUSD", pick: (j) => Number(Object.values(j?.result || {})[0]?.c?.[0]) },
|
||||
{ name: "bitstamp", url: "https://www.bitstamp.net/api/v2/ticker/bchusd/", pick: (j) => Number(j?.last) },
|
||||
{ name: "binance", url: "https://api.binance.com/api/v3/ticker/price?symbol=BCHUSDT", pick: (j) => Number(j?.price) },
|
||||
{ name: "coingecko", url: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin-cash&vs_currencies=usd", pick: (j) => Number(j?.["bitcoin-cash"]?.usd) },
|
||||
];
|
||||
let priceCache = { at: 0, body: null };
|
||||
async function getPrice() {
|
||||
if (priceCache.body && Date.now() - priceCache.at < 60_000) return priceCache.body;
|
||||
const results = await Promise.allSettled(PRICE_SOURCES.map(async (src) => {
|
||||
const r = await fetch(src.url, { signal: AbortSignal.timeout(5000), headers: { "user-agent": "bns-gateway/1", accept: "application/json" } });
|
||||
if (!r.ok) throw new Error(String(r.status));
|
||||
const usd = src.pick(await r.json());
|
||||
if (!Number.isFinite(usd) || usd <= 0) throw new Error("no price");
|
||||
return { name: src.name, usd: Math.round(usd * 100) / 100 };
|
||||
}));
|
||||
const ok = results.filter((r) => r.status === "fulfilled").map((r) => r.value).sort((a, b) => a.usd - b.usd);
|
||||
if (ok.length) {
|
||||
const mid = Math.floor(ok.length / 2);
|
||||
const usd = ok.length % 2 ? ok[mid].usd : Math.round((ok[mid - 1].usd + ok[mid].usd) * 50) / 100;
|
||||
priceCache = { at: Date.now(), body: { usd, sats_per_usd: Math.round(1e8 / usd), sources: ok, median_of: ok.length, at: new Date().toISOString(), stale: false } };
|
||||
return priceCache.body;
|
||||
}
|
||||
if (priceCache.body) return { ...priceCache.body, stale: true };
|
||||
return { usd: null, sats_per_usd: null, sources: [], median_of: 0, at: new Date().toISOString(), stale: true, error: results.map((r) => r.reason?.message).join("; ") };
|
||||
}
|
||||
|
||||
async function activeListings() {
|
||||
if (_marketCheck.list && Date.now() - _marketCheck.at < 30000) return _marketCheck.list;
|
||||
const stored = readMarket();
|
||||
|
|
@ -1138,6 +1167,22 @@ const server = http.createServer(async (req, res) => {
|
|||
}
|
||||
|
||||
// --- TLD certificate UTXO (for co-signed registrations) ---
|
||||
// GET /api/price -> { usd, sats_per_usd, sources: [{name, usd}], median_of, at, stale }
|
||||
// BCH/USD from several exchanges' public endpoints (no keys), median of
|
||||
// whoever answers, cached 60 s; the last good answer is served (marked
|
||||
// stale) if every source fails. Clients also query the same sources
|
||||
// themselves when this route is unreachable.
|
||||
if (req.url === "/api/price" || req.url.startsWith("/api/price?")) {
|
||||
res.setHeader("access-control-allow-origin", "*");
|
||||
res.setHeader("content-type", "application/json");
|
||||
res.setHeader("cache-control", "public, max-age=30");
|
||||
try {
|
||||
const q = await getPrice();
|
||||
res.writeHead(q.usd ? 200 : 503); res.end(JSON.stringify(q, null, 2));
|
||||
} catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /api/tld-cert/<tld> -> { tld, owner, utxo: {txid, vout, satoshis, lockingBytecode, token} }
|
||||
if (req.url.startsWith("/api/tld-cert/")) {
|
||||
res.setHeader("access-control-allow-origin", "*");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue