memoscan
← all memos

Memo 0xf366848f…7b57bd on Ethereum

(m)):(m.k||'—'); /* Each listing IS an ERC-721, and for a deployed token on this chain it is minted to that token's own address - so `ownerOf` is the subject itself. Native and reserved listings have no subject address to hold anything, so the registry holds those. Getting this wrong would point "holder" at address 0, which is a real listing id here, so the zero account is checked rather than assumed absent. */ const ZERO=/^0x0{40}$/i; const subject=m=>m.x&&m.a&&!ZERO.test(m.a)?m.a:''; const holder=m=>subject(m)||L; /* OpenSea renders the listing's own tokenURI, so this is the onchain SVG as a marketplace draws it - the same bytes, somewhere else. */ const osea=m=>'https://opensea.io/assets/ethereum/'+L+'/'+m.i; /* Point Etherscan at the token contract when there is one - that contract is also the holder - and at the listing NFT otherwise, rather than at a zero address. */ const escan=m=>subject(m)?'https://etherscan.io/token/'+subject(m):'https://etherscan.io/nft/'+L+'/'+m.i; function flags(m){let f=[]; /* A listing with no account yet: either its token is undeployed, or it exists somewhere that has not been bridged here. The id is already permanent, which is what a reservation is for, so it reads as RESERVED rather than as an error. */ if(m.x===false)f.push(['at','reserved']); if(m.f)f.push(['nc','sealed']); if(m.o&&COLL(m))f.push(['on','onchain token svg']); return f} function card(r){let[cc,ct]=chip(r.m),m=r.m,a=m.a||'',c=hue(m), pct=Math.round(r.rank*100), badges=(r.img?'':`<span class="c ${cc}">${ct}</span>`)+flags(m).map(([k,t])=>`<span class="c ${k}">${t}</span>`).join(''); /* The figure is no longer role=button: it holds real controls (copy, details and two outbound links), and a button containing buttons is neither valid nor navigable. Pointer users still get the whole card as a target; keyboard users tab to the explicit `details` control instead of onto a figure pretending to be one. */ return `<figure class=p data-i="${r.i}"${c?` style="--b:${c}"`:''}><div class="art${r.img?'':' sk'}">${r.img?`<img src="${esc(r.img)}" alt="${esc(m.s||'listing')} card" decoding=async>`:''}</div><figcaption> <div class=r>${safeImg(m.l)?`<img class=lg src="${esc(safeImg(m.l))}" alt="" decoding=async>`:''}<span class=sy>${esc(m.s||'--')}</span><span class=sd>${esc(m.p||'')}</span></div> ${/* The rendered card already prints the provenance badge AND the weight, so echoing either below the art states the same thing twice — and on a narrow screen the lone right-aligned weight wrapped onto a line of its own with nothing beside it. This row now carries only what the art does NOT say, and disappears when there is nothing to say. The bar keeps the number in its tooltip. */''} ${badges?`<div class=r>${badges}</div>`:''} ${/* The art already prints the raw weight; what it cannot show is where this listing sits among the others. An unlabelled bar at 100% also read as a divider rather than a meter, so the ordinal both informs and explains the bar. */''} <div class=rkw><span class=rkn>#${r.pos}</span><div class=rk role=img aria-label="curation rank ${r.pos} of ${ROWS.length}, weight ${esc(m.r??'0')}" title="curation weight ${esc(m.r??'0')}"><i style="width:${pct}%"></i></div></div> <button class=cp data-a="${esc(a)}" title="${a?'copy address':'no account yet'}">${esc(a||'no account yet')}</button> <div class=ln><button class="lk dtl" data-i="${r.i}">details</button> · <a href="${esc(osea(m))}" target=_blank rel="noopener noreferrer" title="the listing NFT, rendered from its onchain SVG — non-transferable">opensea</a> · <a href="${esc(escan(m))}" target=_blank rel="noopener noreferrer" title="${subject(m)?'the token contract — also the holder of this listing':'the listing NFT'}">etherscan</a></div> </figcaption></figure>`} /* Counted from ROWS, so the strip can never drift from the grid it describes. */ function summary(){let n=ROWS.length,by=k=>ROWS.filter(k).length, kinds=[...new Set(ROWS.map(r=>r.m.p).filter(Boolean))], /* `k:c` is not a chain identity outside EVM: the renderer's namespace is only eip155 | solana | raw, and non-EVM listings are forced to chainId 0 — so every Bitcoin-rooted format AND any future non-EVM kind would collapse into one bucket called `raw:0`. Fall back to the taxonomy group, which does know that Runes, Ordinals, BRC-20 and Tacit are all one chain. */ chains=new Set(ROWS.map(r=>groupOf(r.m))), cells=[[n,'listings'],[by(r=>r.m.v),'read onchain'],[by(r=>r.m.p==='ERC-20'),'erc-20'], [by(r=>COLL(r.m)),'collections'],[by(r=>r.m.f),'sealed'],[chains.size,chains.size===1?'chain':'chains']]; $('sm').innerHTML=cells.map(([v,l])=>`<div><b>${v}</b><span>${esc(l)}</span></div>`).join(''); $('sm').hidden=false;chips()} /* Counting is the same question at both tiers, so ask it once. */ const tally=f=>ROWS.filter(f).length; /* Which group's standards the sub-row should show: the selected group, the group of the selected standard, or this chain's as the resting default. */ const curGroup=()=>KIND.includes('|')?KIND.split('|')[0]:GROUP(KIND)?KIND:'c1'; function inGroup(m,g){return groupOf(m)===g} /* Two tiers, because the taxonomy has two. The top row is fixed so a category can be advertised at zero — that is how someone learns Runes belong here before any Rune is listed. The sub-row only unfolds for the selected group, so the fixed vocabulary never costs the common case a wall of chips. A zero count is shown, not hidden: "bitcoin 0" is information, an absent chip is not. */ function chipHtml(k,label,n,on,owns){ return `<button data-f="${esc(k)}" aria-pressed="${on}" class="${n?'':'z'}${owns?' pa':''}">${esc(label)}<b>${n}</b></button>`} function chips(){ /* Chains present, mainnet first then by id, so the row does not reshuffle as counts move. `bitcoin` is always offered because its formats are a closed enum; solana and other only when something is actually there. */ let seen=new Set(ROWS.map(r=>groupOf(r.m))), chainGroups=[...seen].filter(g=>/^c\d+$/.test(g)) .sort((a,b)=>(+a.slice(1)===1?-1:+b.slice(1)===1?1:+a.slice(1)-+b.slice(1))), groups=[...new Set([...chainGroups,'c1','bitcoin',...(seen.has('solana')?['solana']:[]), ...(seen.has('other')?['other']:[])])], top=[['all','all',ROWS.length], ...groups.map(g=>[g,groupLabel(g),tally(r=>inGroup(r.m,g))]), ['reserved','reserved',tally(r=>r.m.x===false)], ['v','read onchain',tally(r=>r.m.v)]]; /* Mark the group that owns the active standard, so `ethereum` does not read as unselected while `erc-20` is the live filter one row below it. */ $('fc').innerHTML=top.map(([k,l,n])=>chipHtml(k,l,n,String(k===KIND),KIND.startsWith(k+'|'))).join(''); /* The sub-row is always on screen: narrowing to ERC-20 or ERC-721 is a thing people come here to do, and it should not be reachable only by first discovering that `ethereum` unfolds. It shows the selected group's standards, defaulting to the Ethereum ones — so the common filters are visible at load, with none pressed. */ /* groupOf, not "is it present": selecting a standard with no listings yet must keep its own group open, or clicking `rune 0` silently throws you back to Ethereum. */ /* A standard on its own is ambiguous once more than one chain is listed — "erc-20" has to mean "erc-20 ON THIS CHAIN", or selecting it under `base` would show mainnet's too and the sub-row counts would be global. So a sub-chip carries its group: `c8453|ERC-20`. */ let g=curGroup(); $('fc2').innerHTML=GROUP(g).map(x=>chipHtml(g+'|'+x,x.toLowerCase(), tally(r=>r.m.p===x&&groupOf(r.m)===g),String(KIND===g+'|'+x))).join(''); $('fc2').hidden=false} /* Built once; filtering only toggles [hidden]. Re-running innerHTML per keystroke would re-decode every inline SVG - the costliest thing here - and re-flash the art. */ let EMPTY=null,KIND='all'; function render(){ /* A filter chosen against the previous data set would otherwise persist across a reload and hide everything, with only "no listing matches" to explain it. */ KIND='all';summary(); $('grid').innerHTML=ROWS.map(card).join(''); EMPTY=document.createElement('p');EMPTY.className='ct';EMPTY.hidden=true; EMPTY.textContent='No listing matches that filter.';$('grid').append(EMPTY); paint()} /* Sorting sets each card's grid `order` rather than re-emitting the grid: the DOM and its decoded art stay put, so re-sorting costs a style recalc instead of a rebuild. */ /* Narrowing to one standard is a filter's job, so the standard chips are always on screen rather than behind a drill-down. Sorting by standard stays for the open-ended case — browsing a long list grouped by kind — and follows the registry's enum order, not the alphabet: A-Z would wedge BRC-20 between ERC-1155 and ERC-20 and scatter the Bitcoin formats through the Ethereum ones. Anything outside the taxonomy sorts last, so a future standard appears as a tail group instead of displacing Native. */ const ENUM=[...EVMS,...BTCS]; const ord=p=>{let i=ENUM.indexOf(p);return i<0?ENUM.length:i}; const SORTS={rank:(a,b)=>(b.m.r||0)-(a.m.r||0)||cmp(a,b), symbol:cmp,name:(a,b)=>String(a.m.n||'').localeCompare(String(b.m.n||''))||cmp(a,b), standard:(a,b)=>ord(a.m.p)-ord(b.m.p)||(b.m.r||0)-(a.m.r||0)||cmp(a,b), decimals:(a,b)=>(Number(b.m.d)||0)-(Number(a.m.d)||0)||cmp(a,b)}; function cmp(a,b){return String(a.m.s||'').localeCompare(String(b.m.s||''))} function paint(){let f=$('q').value.trim().toLowerCase(),n=0, /* Only real cards: the loading skeletons share `.p` for layout but carry no `data-i`, so a keystroke during the first load used to look them up in ROWS, get undefined and throw. Select by the attribute that makes a card a card. */ els=$('grid').querySelectorAll('.p[data-i]'), keep=r=>(!f||r.q.includes(f))&&(KIND==='all'||(KIND==='v'?r.m.v :KIND==='reserved'?r.m.x===false :KIND.includes('|')?(g=>inGroup(r.m,g[0])&&r.m.p===g[1])(KIND.split('|')) :inGroup(r.m,KIND))); for(let el of els){let r=ROWS[el.dataset.i];if(!r)continue;let show=keep(r);el.hidden=!show;if(show)n++} let order=ROWS.slice().sort(SORTS[$('sort').value]||SORTS.rank); /* Guard the lookup: a failed re-boot empties the grid, and without this the next keystroke indexes past the end of an empty NodeList and throws. */ order.forEach((r,pos)=>{let el=els[r.i];if(el)el.style.order=pos}); if(EMPTY)EMPTY.hidden=!!n||!ROWS.length; $('cnt').textContent=n+(n===ROWS.length?'':' of '+ROWS.length)+(n===1?' listing':' listings')} $('sort').onchange=paint; /* One handler for both rows: the tiers differ in what they offer, not in what a click means. Re-rendering the chips keeps counts, pressed state and the unfolded sub-row in agreement without three places having to remember each other. */ function onChip(e){let b=e.target.closest('button');if(!b)return; KIND=b.dataset.f;chips();paint()} $('fc').addEventListener('click',onChip); $('fc2').addEventListener('click',onChip); /* rAF-coalesced so a fast typist repaints once per frame, not once per keystroke. */ let tick=0; $('q').oninput=()=>{if(tick)return;tick=requestAnimationFrame(()=>{tick=0;paint()})}; function open_(i){let r=ROWS[i];if(!r)return;CUR=r; $('dkv').hidden=$('dla').hidden=false;$('dda').hidden=$('dnote').hidden=true; let m=r.m; $('dt').textContent=(m.s||'--')+' · listing '+m.i; /* The registry already carries desc, url, audit and extras; they were being fetched on every load and then thrown away. Show them rather than re-read them elsewhere. */ $('ddesc').textContent=m.desc||'';$('ddesc').hidden=!m.desc; let rows=[['name',m.n||'-'],['account',m.a||'(not deployed)'],['chain',chainLabel(m)], ['standard',m.p||'unknown'],['decimals',m.d],['curation weight',m.r], /* Same call the card makes, so the two can never disagree about a listing. */ ['provenance',chip(m)[1]], ['authoring',m.f?'sealed — cannot be re-authored':'open to the curator'], ['brand colour',hue(m)||'-']]; rows.push(['holder',holder(m)+(subject(m)?' — the token itself':' — the registry')]); /* Stated plainly because this page links out to a marketplace: every listing is born locked and stays locked, so a visitor who sees it on OpenSea should not have to discover from a reverted transaction that it was never for sale. */ rows.push(['transfer','locked — soulbound (ERC-5192)']); if(COLL(m))rows.push(['token art',m.o?'ids resolve to onchain svg':'not declared onchain']); if(m.u)rows.push(['url',m.u]); if(m.au)rows.push(['audit',m.au]); /* Extras are curator-authored key/value pairs, so their count and keys are unknown ahead of time - render whatever is there instead of a fixed set. */ for(let x of (Array.isArray(m.e)?m.e:[]))rows.push([x.k||'extra',x.v||'']); $('dkv').innerHTML=rows.map(([k,v])=>`<span>${esc(k)}</span><span>${esc(v)}</span>`).join(''); $('dlk').innerHTML=`<a href="${esc(osea(m))}" target=_blank rel="noopener noreferrer">view on opensea</a> · <a href="${esc(escan(m))}" target=_blank rel="noopener noreferrer">${subject(m)?'token on etherscan':'listing on etherscan'}</a>${safeUrl(m.u)?` · <a href="${esc(safeUrl(m.u))}" target=_blank rel="noopener noreferrer">project site</a>`:''}`; $('dj').textContent=JSON.stringify(r.m,null,1); document.querySelector('.db').scrollTop=0; if(!$('dlg').open)$('dlg').showModal()} /* Copying has two paths because the async Clipboard API is permission-gated: it is refused outright in a sandboxed frame and in several in-app wallet browsers. The legacy execCommand path is driven by the user gesture and needs no permission, so it still lands in most of those. If both fail, select the text — a reader can always copy a selection by hand, and "copy failed" with no recourse is a dead end. */ async function copyText(t,el){ try{await navigator.clipboard.writeText(t);return true}catch(e){} try{let ta=document.createElement('textarea');ta.value=t;ta.setAttribute('readonly',''); ta.style.cssText='position:fixed;top:0;left:-9999px'; document.body.appendChild(ta);ta.select(); let ok=document.execCommand('copy');ta.remove();if(ok)return tru