// What a dollar is worth in POL right now. // // Mission blocks are priced in dollars ($30 entry, $10 listing) because that is how Marty thinks // about them and how a buyer judges them. The faucet pays in POL. So something has to convert, // and it has to be a rate nobody can argue with. // // It is read from the InstantAdPay contract's own quote: the contract stores each package's price // in cents and quotes the POL to pay right now, so priceCents / quoteWei IS the rate members are // already paying at today. No third-party price feed, no oracle to trust, no disagreement between // what a mission costs here and what a package costs there. // // Cached for a few minutes and fail-soft: a purchase is refused rather than priced off a stale or // missing rate, because getting this wrong silently means selling delivery below cost. 'use strict'; const https = require('https'); const IAP = '0xbe1eca72aff47d13d8e907e523f55eb9e2d365e0'; const RPCS = ['https://polygon-bor-rpc.publicnode.com', 'https://1rpc.io/matic']; const SEL_COUNT = '0xe0f6ef87'; // productCount() const SEL_PRODUCT = '0xbf712fd6'; // products(uint32) -> priceCents is word 0, active word 3 const SEL_QUOTE = '0x7de85694'; // quoteWei(uint32) const TTL = 5 * 60000; let cache = { usdPerPol: 0, at: 0 }; function rpc(method, params) { const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }); return new Promise((resolve, reject) => { let i = 0; const attempt = () => { const u = new URL(RPCS[i]); const rq = https.request({ hostname: u.hostname, path: u.pathname, method: 'POST', timeout: 12000, headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { const j = JSON.parse(d); if (j.error) throw new Error(j.error.message); resolve(j.result); } catch (e) { if (++i < RPCS.length) attempt(); else reject(e); } }); }); rq.on('error', () => { if (++i < RPCS.length) attempt(); else reject(new Error('rpc unreachable')); }); rq.on('timeout', () => { rq.destroy(); }); rq.end(body); }; attempt(); }); } const call = data => rpc('eth_call', [{ to: IAP, data }, 'latest']); const enc = n => BigInt(n).toString(16).padStart(64, '0'); const word = (r, i) => r.slice(2 + i * 64, 2 + (i + 1) * 64); // Walk the catalogue until a product quotes a non-zero POL cost, then take cents / POL from it. async function fetchRate() { const n = parseInt(word(await call(SEL_COUNT), 0), 16); for (let id = 1; id <= Math.min(n, 12); id++) { let wei = 0n, cents = 0; try { wei = BigInt('0x' + word(await call(SEL_QUOTE + enc(id)), 0)); } catch (e) { continue; } if (wei <= 0n) continue; try { const p = await call(SEL_PRODUCT + enc(id)); cents = parseInt(word(p, 0), 16); // priceCents const active = parseInt(word(p, 3), 16) === 1; const exists = parseInt(word(p, 4), 16) === 1; if (!active || !exists) cents = 0; } catch (e) { /* fall through */ } if (!cents) continue; const pol = Number(wei / 10n ** 12n) / 1e6; if (pol > 0) return (cents / 100) / pol; // dollars per POL } throw new Error('no product quoted a usable price'); } // dollars per POL, e.g. 0.1019 async function usdPerPol(force) { if (!force && cache.usdPerPol > 0 && Date.now() - cache.at < TTL) return cache.usdPerPol; const r = await fetchRate(); if (!(r > 0) || !isFinite(r)) throw new Error('rate came back unusable'); cache = { usdPerPol: r, at: Date.now() }; return r; } // POL needed for a price in cents. Throws rather than guessing: a purchase priced off a rate we // could not read is a purchase that might sell delivery below cost. async function polForCents(cents) { const rate = await usdPerPol(); const pol = (Number(cents) / 100) / rate; return Math.round(pol * 10000) / 10000; } module.exports = { usdPerPol, polForCents, _cache: () => cache };