memoscan
← all memos

Memo 0xf221a866…0f8371 on Ethereum

DOS_loader var base = Math.min(20000, 500 * Math.pow(2, attempt || 0)); ep.cooldownUntil = Date.now() + base / 2 + Math.random() * (base / 2); } }; var rpcPool = window.rpcPool; window.rpcSleep = function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); }; var rpcSleep = window.rpcSleep; // First working RPC from the RPCs array: first endpoint not cooling down // (i.e. not recently failed / rate-limited), else the first configured one. // Use this wherever a single endpoint is needed (e.g. callContract). window.firstWorkingRpc = function () { var eps = window.rpcPool.endpoints(); var now = Date.now(); for (var i = 0; i < eps.length; i++) if (eps[i].cooldownUntil <= now) return eps[i].url; return eps.length ? eps[0].url : ""; }; // Run fn(item, rpcUrl) over items with one worker per endpoint (recompose's // worker-pool U()). Failed items retry on another endpoint up to `retries`. window.parallelRpcMap = async function (items, fn, opts) { var retries = (opts && opts.retries != null) ? opts.retries : 3; var eps = rpcPool.endpoints(); if (!eps.length) { // no pool — degrade to sequential on the first rpc for (var k = 0; k < items.length; k++) { try { await fn(items[k], window.firstWorkingRpc()); } catch (e) { console.warn('parallelRpcMap:', items[k], e); } } return; } var next = 0; var workers = Array.from({ length: Math.min(eps.length, items.length) }, async function () { while (next < items.length) { var i = next++; for (var a = 0; ; a++) { var picked = rpcPool.pick(); if (picked.waitMs > 0) await rpcSleep(picked.waitMs); try { await fn(items[i], picked.ep.url); break; } catch (e) { rpcPool.cooldown(picked.ep, a); if (a >= retries) { console.warn('parallelRpcMap: item failed', items[i], e); break; } } } } }); await Promise.all(workers); }; /* --------------------------------------------- scripty getContent (eth_call) */ // Self-contained minimal ABI encode/decode: the loader runs BEFORE // COS_ethCalls.js (which is itself in the script list above). // ABI-encode getContent(string name, bytes data) with data == empty bytes (0x). // Head: two offset words; tail: string (len + padded utf8), then bytes (len 0). function encodeGetContent(name) { function word(n) { return n.toString(16).padStart(64, "0"); } var utf8 = new TextEncoder().encode(name); var hex = ""; for (var i = 0; i < utf8.length; i++) hex += utf8[i].toString(16).padStart(2, "0"); var padded = hex.padEnd(Math.max(64, Math.ceil(hex.length / 64) * 64), "0"); var strTail = word(utf8.length) + padded; // string: len + data return GETCONTENT_SELECTOR + word(0x40) + // offset of string word(0x40 + strTail.length / 2) + // offset of bytes strTail + word(0); // bytes: len 0 (== '0x') } // Decode a single ABI `bytes` return into a 0x-prefixed hex blob. function decodeBytesReturn(result) { if (!result || result === "0x") throw new Error("empty result"); var h = result.slice(2); var off = parseInt(h.slice(0, 64), 16) * 2; var len = parseInt(h.slice(off, off + 64), 16) * 2; if (!len) throw new Error("content not stored"); return "0x" + h.slice(off + 64, off + 64 + len); } // One eth_call: ScriptyStorageV2.getContent(name, 0x) on a given endpoint. async function ethGetContent(name, rpcUrl) { var payload = { jsonrpc: "2.0", method: "eth_call", params: [{ to: SCRIPTY_ADDRESS, data: encodeGetContent(name) }, "latest"], id: 1 }; var r = await fetch(rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (!r.ok) throw new Error("HTTP " + r.status); var json = await r.json(); if (json.error) throw new Error("RPC Error: " + json.error.message); return decodeBytesReturn(json.result); } /* ------------------------------------------------- parallel prefetch pool */ // Same principle as recompose.min.js / the world's slot loader: all // getContent calls are dispatched up-front through the RPC pool (one worker // per endpoint, paced, retried across endpoints with cooldown), while // INJECTION stays strictly sequential — script order matters for execution, // but the network wait is fully overlapped. function prefetchAll(scripts) { var RETRIES = 3; var resolvers = scripts.map(function () { return {}; }); var promises = scripts.map(function (_, i) { return new Promise(function (res, rej) { resolvers[i].res = res; resolvers[i].rej = rej; }); }); async function fetchOne(sc) { var lastErr = null; var hasPool = rpcPool.endpoints().length > 0; for (var a = 0; hasPool && a <= RETRIES; a++) { var picked = rpcPool.pick(); if (picked.waitMs > 0) await rpcSleep(picked.waitMs); try { return await ethGetContent(sc.data, picked.ep.url); } catch (e) { lastErr = e; rpcPool.cooldown(picked.ep, a); } } // Local-dev fallback: `data` may be a plain URL/path (e.g. a .txt or .gz // served next to the page). Try a direct fetch before giving up. try { var r = await fetch(sc.data); if (!r.ok) throw new Error("HTTP " + r.status); return await r.text(); } catch (e) { throw lastErr || e; } } var next = 0; function worker() { if (next >= scripts.length) return Promise.resolve(); var i = next++; return fetchOne(scripts[i]) .then(function (txt) { resolvers[i].res(txt); }) .catch(function (err) { resolvers[i].rej(err); }) .then(worker); } var workers = []; var n = Math.max(1, Math.min(rpcPool.endpoints().length || 1, scripts.length)); for (var w = 0; w < n; w++) workers.push(worker()); return promises; } /* -------------------------------------------------------------------- finish */ var finished = false; function finish() { if (finished) return; finished = true; markDone(curLine); setProgress(1); if (!el.root) return; el.root.classList.add("dos-hide"); setTimeout(function () { if (el.root && el.root.parentNode) el.root.parentNode.removeChild(el.root); }, 900); } // Wait for the world to signal readiness (window.__WORLD_READY, set at the end // of init() once the scene is built and the render loop has started). // // The modules finish injecting well before the world is actually painted (the // world spends a few seconds fetching slot data + building geometry). Rather // than fade on a timer and expose a white screen, we keep the splash up and // show a "World reconstruction" phase: the radar keeps sweeping and the counter // shows the elapsed reconstruction time, until __WORLD_READY (or the hard cap). function awaitWorld() { if (window.__WORLD_READY) return finish(); // Stop the progress easing so it doesn't fight the reconstruction spin. if (raf) { cancelAnimationFrame(raf); raf = 0; } addLine("World reconstruction"); var start = Date.now(); var spinRaf = 0; (function spin() { var elapsed = (Date.now() - start) / 1000; // ~0.55 turn/sec continuous sweep (indeterminate — we can't know the total). if (el.radar) el.radar.setAttribute("transform", "rotate(" + ((elapsed * 200) % 360).toFixed(2) + " 256 256)"); if (el.pct) el.pct.textContent = elapsed.toFixed(1) + "s"; spinRaf = requestAnimationFrame(spin); })(); (function poll() { if (window.__WORLD_READY || Date.now() - start > READY_TIMEOUT_MS) { if (spinRaf) cancelAnimationFrame(spinRaf); return finish(); } setTimeout(poll, 120); })(); } /* ---------------------------------------------------------------------- boot */ async function boot() { // Run-once guard: modules are injected as classic <script>s into the shared // global scope, so a second boot would re-declare their top-level consts // (e.g. `fonts` from CypherDudesFonts) and throw "already declared". This can // happen when the same window is reused (e.g. a document.write preview that // reopens the document and re-fires DOMContentLoaded). if (window.__DOS_BOOTED) return; window.__DOS_BOOTED = true; build(); window.addEventListener("resize", layout); window.addEventListener("load", layout); if (document.fonts && document.fonts.ready) document.fonts.ready.then(layout); layout(); var n = SCRIPTS.length; // All network fetches start NOW, in parallel; the loop below only waits // for each script's bytes to arrive before injecting them in order. var prefetched = prefetchAll(SCRIPTS); for (var i = 0; i < n; i++) { addLine(SCRIPTS[i].label); // new name on the first line, previous pushed down setProgress(i / n); try { var txt = await prefetched[i]; await injectOne(txt, SCRIPTS[i]); } catch (err) { console.error("[DOSLoader] failed:", SCRIPTS[i].data, err); markError(); } setProgress((i + 1) / n); // MonoPixel changes the title metrics -> re-fit once it has loaded if (/fonts/i.test(SCRIPTS[i].data)) layout(); } markDone(curLine); // Everything is injected — DOS_animate's init() is building the world NOW. // Signal listeners (the recompose zk-preload prelude) to start their // BACKGROUND downloads (crypto/snarkjs/wasm/zkey) without having competed // with the boot fetches above. try { window.dispatchEvent(new Event("dos:world-ready")); } catch (e) {} awaitWorld(); } // Public API window.DOSLoader = { setProgress: setProgress, addLine: addLine, layout: layout, finish: finish, boot: boot }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();