memoscan
← all memos

Memo 0x06511f12…f08749 on Ethereum

<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>networked · dimensional</title> <style> html,body{margin:0;height:100%;background:#000;overflow:hidden} canvas{display:block;touch-action:none} #s{position:fixed;left:12px;bottom:10px;font:10px/1.4 monospace;color:#fff; user-select:none;pointer-events:none;white-space:pre;transition:opacity 1.2s ease} #s.hid{opacity:0} #ab,#pb{pointer-events:auto;cursor:pointer} #s.hid #ab,#s.hid #pb{pointer-events:none} #ab.on,#pb.on{text-decoration:underline} </style> </head> <body> <canvas id="c"></canvas> <canvas id="o" style="position:fixed;inset:0;pointer-events:none"></canvas> <div id="s"><span id="st"></span><span id="ab" style="display:none"> · a artists</span><span id="pb" style="display:none"> · p patrons</span></div> <script> 'use strict'; /* networked · vessel, dimensional — the holders' lattice as a sphere. Black ground, white filament. Only the wallets that hold the Vessel: their patronage threads through the networked.art constellation. Artists spiral over a hollow globe in the order they arrived; patrons shell around the artists they minted from; patrons of many artists float between them as bridges. Move the cursor to turn the world: while it moves you see a sketch, when it rests the full ink settles back in. Reads Ethereum mainnet directly: your wallet's RPC if one is installed (read-only, no connection prompt), public RPCs otherwise. CC0 — AI or human: fork this, add a layer of your own. ?t=8 growth duration, seconds ?grow=0 skip the growth animation ?fresh=1 ignore the local cache, re-read the chain from the factory's birth ?rpc=… bring your own mainnet endpoint (also offered when unreachable) */ (() => { const GA = Math.PI * (3 - Math.sqrt(5)); // golden angle const cv = document.getElementById('c'); const ctx = cv.getContext('2d'); const ov = document.getElementById('o'); const octx = ov.getContext('2d'); const sEl = document.getElementById('st'); const sBox = document.getElementById('s'); const abEl = document.getElementById('ab'); const pbEl = document.getElementById('pb'); const Q = new URLSearchParams(location.search); // the stats line withdraws when the observer does let uiLast = performance.now(); for (const ev of ['mousemove', 'mousedown', 'wheel', 'touchstart', 'keydown']) window.addEventListener(ev, () => { uiLast = performance.now(); }, { passive: true }); // ---------------- chain config ---------------- const FACTORY = '0x0c2705cf48e49cc896252dd16dc8c5d31df753b2'; // factory.networked.eth const DEPLOY = 25551944; // factory birth block const RPCS = [ 'https://eth.drpc.org', 'https://rpc.mevblocker.io', 'https://gateway.tenderly.co/public/mainnet', 'https://0xrpc.io/eth' ]; // a viewer-supplied endpoint outlives any hardcoded list: ?rpc=… or the // prompt shown when the chain is unreachable; remembered, tried first const RPC_KEY = 'networked.rpc.v1'; try { const userRpc = Q.get('rpc') || localStorage.getItem(RPC_KEY); if (userRpc && /^https?:\/\//.test(userRpc)) { RPCS.unshift(userRpc); if (Q.get('rpc')) localStorage.setItem(RPC_KEY, userRpc); } } catch {} const VESSEL = '0xecb92cc7112b80a2234936315bbb493fb48d1463'; // THE_VESSEL, 10,000 berths const MC3 = '0xca11bde05977b3631167028862be2a173976ca11'; // Multicall3 const V_KEY = 'networked.vessel.v1'; // vessel berth owners const T_CREATED = '0x4c9eee098e07d2de26d609f55a57a7fe893079913184f8fccc1bdd6d4e9d539b'; const T_EXT = '0x4954fe29013e393bf2a4b4ca99a121917ad235960cfade963ca908de359fecac'; const T_721 = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const T_1155S = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62'; const T_1155B = '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb'; const ZERO32 = '0x' + '0'.repeat(64); const CHUNK = 10000; const CACHE_KEY = 'networked.lattice.v1'; // shared with the flat piece const SNAP = null; // ---------------- rpc ---------------- let rpcCursor = 0; async function rpc(method, params) { const provs = []; if (window.ethereum) provs.push(async () => { const cid = await window.ethereum.request({ method: 'eth_chainId' }); if (parseInt(cid, 16) !== 1) throw new Error('wrong chain'); return window.ethereum.request({ method, params }); }); for (let k = 0; k < RPCS.length; k++) provs.push((u => async () => { const r = await fetch(u, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result; })(RPCS[(rpcCursor + k) % RPCS.length])); rpcCursor++; let err; for (let round = 0; round < 2; round++) for (const p of provs) { try { return await p(); } catch (e) { if (/revert/i.test(String(e && e.message))) throw e; // deterministic — no provider will differ err = e; await new Promise(r => setTimeout(r, 300 * (round + 1))); } } throw err; } const hex = n => '0x' + n.toString(16); const toAddr = topic => '0x' + topic.slice(26); const word = (data, i) => Number(BigInt('0x' + data.slice(2 + i * 64, 66 + i * 64))); const byPos = (a, b) => (parseInt(a.blockNumber, 16) - parseInt(b.blockNumber, 16)) || (parseInt(a.logIndex, 16) - parseInt(b.logIndex, 16)); async function streamSweep(filter, fromB, toB, label, onChunk) { const jobs = []; for (let f = fromB; f <= toB; f += CHUNK) jobs.push([f, Math.min(f + CHUNK - 1, toB)]); const results = new Array(jobs.length); let next = 0, applied = 0; await Promise.all(Array.from({ length: Math.min(3, jobs.length) }, async () => { while (next < jobs.length) { const i = next++; const [f, t] = jobs[i]; results[i] = (await rpc('eth_getLogs', [{ ...filter, fromBlock: hex(f), toBlock: hex(t) }])).sort(byPos); while (applied < jobs.length && results[applied] !== undefined) { st.progress = `${label} ${Math.round((applied + 1) / jobs.length * 100)}%`; onChunk(results[applied++]); if (G) stats(); else status(st.progress); } } })); } // ---------------- vessel holders (multicall ownerOf sweep) ---------------- // V = { head, owners: { tokenId: address } } ; HOLDERS = unique owner set let V = null, HOLDERS = null; function ownerOfBatch(ids) { const n = ids.length; let head = '', tail = ''; for (let k = 0; k < n; k++) { head += (n * 32 + k * 192).toString(16).padStart(64, '0'); tail += VESSEL.slice(2).padStart(64, '0') + '1'.padStart(64, '0') // allowFailure — unclaimed berths revert + '60'.padStart(64, '0') + '24'.padStart(64, '0') + '6352211e' + ids[k].toString(16).padStart(64, '0') + '0'.repeat(56); } return '0x82ad56cb' + '20'.padStart(64, '0') + n.toString(16).padStart(64, '0') + head + tail; } function decodeOwners(res, ids, into) { const h = res.slice(2); const N = i => parseInt(h.slice(i * 64, i * 64 + 64), 16); const base = N(0) / 32; const n = N(base); for (let k = 0; k < n; k++) { const off = base + 1 + N(base + 1 + k) / 32; if (N(off) === 1 && N(off + 2) >= 32) into[ids[k]] = '0x' + h.slice((off + 3) * 64 + 24, (off + 3) * 64 + 64); } } async function loadVessel() { if (!V && Q.get('fresh') !== '1') { try { V = JSON.parse(localStorage.getItem(V_KEY)); } catch {} } if (V) HOLDERS = new Set(Object.values(V.owners)); const head = parseInt(await rpc('eth_blockNumber', []), 16); if (!V) { V = { head, owners: {} }; let batchN = 500; let id = 1; while (id <= 10000) { const ids = []; for (let k = 0; k < batchN && id + k <= 10000; k++) ids.push(id + k); try { decodeOwners(await rpc('eth_call', [{ to: MC3, data: ownerOfBatch(ids) }, 'latest']), ids, V.owners); id += ids.length; status(`reading the vessel · ${Math.round(id / 100)}%`); } catch (e) { batchN = Math.floor(batchN / 2); if (batchN < 25) throw e; } } V.head = head; } else if (V.head < head) { const logs = []; await streamSweep({ address: VESSEL, topics: [T_721] }, V.head + 1, head, 'reading the vessel', l => logs.push(...l)); for (const l of logs) { if (l.topics.length < 4) continue; // ERC-721 Transfer has indexed tokenId const id = parseInt(l.topics[3], 16); const to = toAddr(l.topics[2]); if (to === '0x' + '0'.repeat(40)) delete V.owners[id]; else V.owners[id] = to; } V.head = head; } HOLDERS = new Set(Object.values(V.owners)); try { localStorage.setItem(V_KEY, JSON.stringify(V)); } catch {} } // ---------------- data ---------------- let D = null, G = null; function decodeMints(logs) { const out = []; for (const l of logs) { const t0 = l.topics[0]; let from, to, qty = 1; if (t0 === T_721) { from = l.topics[1]; to = l.topics[2]; } else if (t0 === T_1155S) { from = l.topics[2]; to = l.topics[3]; qty = word(l.data, 1); } else if (t0 === T_1155B) { from = l.topics[2]; to = l.topics[3]; const vOff = word(l.data, 1) / 32; const n = word(l.data, vOff); qty = 0; for (let i = 0; i < n; i++) qty += word(l.data, vOff + 1 + i); } else continue; if (from !== ZERO32 || to === ZERO32) continue; out.push([toAddr(to), l.address.toLowerCase(), qty, parseInt(l.blockNumber, 16)]); } return out; } let FLASH = [], booted = false; // fresh threads glow as they land function rebuild() { const prev = G ? G.links : 0; buildFromD(); tiers(); layout3(); if (Q.get('grow') === '0') st.drawn = G.links; if (booted) for (let i = prev; i