0xacfba7ce…4082sent to0x00000000…6f2a·#25,684,018·view on Etherscan
(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