0x54ded708…1627sent to0xbd11994a…7699·#25,543,809·view on Etherscan
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();
}
})();