0x68575b07…67c7·#25,913,257·view on Etherscan
t cpSettleWrap(n);pvAmt.value="";refreshBalance();
stat.textContent="Deposited. The relay is settling it into the pool.";sGot()};
const cpExit=async n=>{
const ch=+pvChain.value,to=pvTo.value.trim()||account,self=pvPath.value==="self",k=cpNoteOf(n);
if(n.ex&&n.ex.job&&n.ex.js!=="failed")throw Error("This note is already exiting.");
if(!isAddr(to))throw Error("The recipient must be a 0x address on "+CHAINS[ch].name+".");
if(!CP_L2[ch])throw Error("Unknown chain.");
const c=await cpSync(true),li=c.leaves.indexOf(k.leaf);
if(li<0)throw Error("This deposit has not settled into the pool yet.");
if(c.spent.includes(k.nu))throw Error("This note is already spent.");
if(c.leaves.length<20)pvNote.textContent="The pool holds only "+c.leaves.length+" notes, so an exit today is easy to link. Treat it as a test, not as privacy.";
const impl=await cpPins(ch);
const fee=self?0n:await cpFee(k.value);
if(!self&&fee>=k.value)throw Error("This note is too small to relay — submit it yourself instead.");
if(!self&&fee*10000n/k.value>300n)throw Error("The relay fee would be "+(Number(fee*10000n/k.value)/100).toFixed(1)+"% of this note — wait for cheaper gas, or submit it yourself.");
const net=k.value-fee,wei=net*CP_SCALE;
const code=await cfgRead("eth_getCode",[to,L]);
const x={ch,to,dl:String((Math.floor(Date.now()/86400000)+3)*86400),nonce:big(sha2(cpU8("zswap-exit-nonce-v1:"+cpFp+":"+n.i))).toString(),
fee:String(fee),wei:String(wei),self,fin:to};
if(strip0x(code||"")!==""){x.fin=cpRescue(n.i).addr;x.rk=1;
if(ch===4663)pvNote.textContent="That recipient has contract code on Ethereum, so Robinhood will refund unused gas to an aliased address rather than to it."}
if(ch===4663){const g=await cpRhGas(to);x.gl=String(g.gl);x.sc=String(g.sc);x.mf=String(g.mf);
const over=g.sc+g.gl*g.mf;if(over>=wei)throw Error("The note would be eaten by Robinhood's gas budget.");x.l2v=String(wei-over)}
const r=cpRecipe(x),esc=cpEscrow(impl,r);
const onchain=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPESCROW+pad32("20")+cpEncRecipe(r)},L]));
if(onchain!==esc)throw Error("Escrow address mismatch — refusing to build a proof.");
x.esc=esc;
const warn=await cpDry(x,r,esc);
const{root,path}=cpTree(c.leaves,li);
const dlu=String(Math.ceil((Math.floor(Date.now()/1000)+3600)/600)*600),cb=cpBinding();
const ctx=cpCtx("tacit-unwrap-intent-v1",cb,CP_ETH,pad32(strip0x(esc)),[[k.cx,k.cy,k.owner]],[k.value,fee,BigInt(dlu)]);
const sig=cpSigma(k.value,k.blinding,ctx,cpNonce(k.blinding,ctx,"unwrap"));
const op={chainBinding:cb,spendRoot:root,asset:CP_ETH,cx:k.cx,cy:k.cy,owner:k.owner,leafIndex:li,path,nk:k.secret,secret:k.secret,
value:String(k.value),recipient:esc,fee:String(fee),deadline:dlu,sigR:sig.R,sigZ:sig.z};
n.ex=x;cpSaveNotes();cpPaint();
const r2=await relayFetch("/confidential/submit",{type:"unwrap",op,memos:[],mode:self?"prove":"settle"});
if(!r2.jobId)throw Error("The relay did not take the exit.");
x.job=r2.jobId;x.js=r2.status||"pending";cpSaveNotes();
stat.textContent=(self?"Proving. The wallet will ask you to send the exit when the proof is ready.":"The relay is settling the exit. Activate it once it lands.")+(warn?" "+warn:"");
if(self)await cpSelfSend(n)};
const cpSelfSend=async n=>{const x=n.ex;
for(let t=0;t<180&&!(x.pv&&x.pr);t++){await new Promise(r=>setTimeout(r,5000));await cpRelayPoll(n);
if(x.js==="failed")throw Error("The prover failed: "+(x.jerr||"unknown"));
if(x.js==="settled")return cpPaint()}
if(!(x.pv&&x.pr))throw Error("The proof is taking a while — it keeps proving; come back and press Send.");
await checkWallet();
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpExitData(x.pv,x.pr,cpRecipe(x)),gas:toHex(2000000)};
await rpc(C,[req,L]);
x.ptx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.ptx);stat.textContent="Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpActivate=async n=>{const x=n.ex,r=cpRecipe(x),now=Math.floor(Date.now()/1000);
if(now>Number(x.dl))throw Error("The exit expired — reclaim it instead.");
if(!await cpEnsureBalance(x.esc,BigInt(x.wei)))throw Error("The escrow is not funded yet — the relay has not settled.");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpActData(r),gas:toHex(CP_L2[x.ch].gas)};
await rpc(C,[req,L]);
x.atx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.atx);stat.textContent="Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpReclaim=async n=>{const x=n.ex,r=cpRecipe(x);
if(Math.floor(Date.now()/1000)<=Number(x.dl))throw Error("Reclaim opens after "+new Date(Number(x.dl)*1000).toLocaleString()+".");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpReclData(r),gas:toHex(400000)};
await rpc(C,[req,L]);
x.rtx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.rtx);stat.textContent="Returned to "+x.fin+" on Ethereum."};
const cpRecover=async()=>{const c=await cpSync(true);let found=0;
for(const[id,amt]of c.wraps){const wei=BigInt(amt);if(wei%CP_SCALE)continue;const v=wei/CP_SCALE;
if(cpNotes.some(n=>BigInt(n.v)===v&&cpDepId(v,cpDepCommit(...(k=>[k.cx,k.cy,k.owner])(cpNoteOf(n))))===id))continue;
for(let i=0;i<64;i++){const k=cpNoteOf({i,v:String(v)});
if(cpDepId(v,cpDepCommit(k.cx,k.cy,k.owner))===id){if(!cpNotes.some(n=>n.i===i))cpNotes.push({i,v:String(v),at:0,rec:1});found++;break}}}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();return found};
const cpFmt=v=>trimAmt(BigInt(v)*CP_SCALE,18);
const cpPaint=()=>{if(!pvMode)return;
const on=!!cpSeed;
pvKey.innerHTML=!account?"Connect a wallet to begin.":!on?"Sign once to unlock the key that owns your deposits.":
`Key unlocked <button type="button" class="fclink" data-a="backup">backup</button><button type="button" class="fclink" data-a="import">import</button><button type="button" class="fclink" data-a="recover">recover</button><button type="button" class="fclink" data-a="export">export</button>`;
pvGo.textContent=!account?"Connect Wallet":!on?"Unlock key":"Deposit";
pvGo.disabled=!!cpBusy;
if(!on){pvList.innerHTML="";return}
const rows=cpNotes.map(n=>{const{s}=cpStatus(n),x=n.ex,ch=x?CHAINS[x.ch].name:"";
const act=s==="ready"?`<button type="button" class="fclink" data-a="exit" data-i="${n.i}">exit</button>`
:s==="pending"?(n.job?(n.js==="failed"?`relay failed<button type="button" class="fclink" data-a="settle" data-i="${n.i}">retry</button>`:"settling…")
:`<button type="button" class="fclink" data-a="settle" data-i="${n.i}">settle</button>`)
:s==="exiting"?(!x.job||x.js==="failed"?`relay failed<button type="button" class="fclink" data-a="exit" data-i="${n.i}">retry</button>`
:x.self?(x.pv?`<button type="button" class="fclink" data-a="send" data-i="${n.i}">send</button>`:"proving…"):"relaying…")
:s==="funded"?`<button type="button" class="fclink" data-a="activate" data-i="${n.i}">activate</button>`
:s==="expired"?`<button type="button" class="fclink" data-a="reclaim" data-i="${n.i}">reclaim</button>`
:s==="bridged"?`<a href="${CHAINS[x.ch].explorer}/address/${x.to}" target="_blank" rel="noreferrer">on ${ch}</a>`:s;
return`<div class="cpr"><span><b>${cpFmt(n.v)} ETH</b> #${n.i}${x&&s!=="bridged"?" → "+ch:""}</span><span>${act}</span></div>`}).join("");
pvList.innerHTML=rows||`<div class="cpr"><span>No deposits yet.</span></div>`};
const pvPaint=()=>{const on=pvMode;
pv.setAttribute("aria-pressed",on?"true":"false");
pv.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
for(const el of [pvPanel,pvGo])el.classList.toggle("hide",!on);
for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("pvon",on)};
function pvSet(on){
if(on)cbDisarm();
++seq;quoting=false;
pvMode=on;
if(on&&lqMode)lqSet(false);
if(on&&lnMode)lnSet(false);
if(on&&wnMode)wnSet(false);
pvPaint();
stat.textContent="";pvNote.textContent="";
clearInterval(pvTimer);
if(on){pvTimer=setInterval(()=>{if(document.hidden||!pvMode)return;pvRefresh()},8000);pvWake()}
fcSync();}
const pvWake=()=>{if(!account){cpSeed="";cpNotes=[]}else if(!cpSeed){const s=cpKeyFor(account);if(/^0x[0-9a-f]{64}$/i.test(s||""))cpUse(s)}
if(account)pvTo.placeholder=account;cpPaint();pvRefresh()};
const pvRefresh=async()=>{if(!cpSeed||cpBusy)return;const my=++pvTick;
try{await cpSync(cpNotes.some(n=>/pending|exiting/.test(cpStatus(n).s)));
for(const n of cpNotes){const t=n.ex||n;if(t.job&&!["settled","failed","proven"].includes(t.js||""))await cpRelayPoll(n)}
if(my===pvTick&&pvMode)cpPaint()}catch(e){try{if(my===pvTick&&pvMode)pvNote.textContent=explain(e)}catch{}}};
const pvRun=async(fn,label)=>{if(cpBusy)return;cpBusy=1;pvGo.disabled=true;stat.textContent=label||"";
try{await checkWallet();await fn();cpSyncAt=0}catch(e){err(e)}
cpBusy=0;cpSaveNotes();cpPaint();pvRefresh()};
pvGo.onclick=async()=>{
if(pvGo.disabled)return;
if(!account)return void connect();
if(!cpSeed)return pvRun(cpUnlock,"Check your wallet for the message to sign.");
pvRun(cpDeposit,"Building the deposit…")};
pvList.onclick=pvKey.onclick=e=>{const b=e.target.closest("button[data-a]");if(!b)return;sBlip();
const a=b.dataset.a,n=cpNotes.find(x=>x.i===+b.dataset.i);
if(a==="backup"){prompt("Your private-bridge key. Anyone holding it can spend your deposits.",cpSeed);return}
if(a==="import"){const s=(prompt("Paste a private-bridge key (0x + 64 hex), or an exported note list.")||"").trim();
if(/^\[/.test(s)){let arr;try{arr=JSON.parse(s)}catch{}
if(!Array.isArray(arr))return void(stat.textContent="That is not a note list.");
let add=0;for(const r of arr)if(r&&Number.isInteger(r.i)&&/^\d+$/.test(r.v||"")&&!cpNotes.some(n=>n.i===r.i)){cpNotes.push(r);add++}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();cpPaint();pvRefresh();stat.textContent=add?"Imported "+add+" note"+(add>1?"s":"")+".":"Nothing new in that list.";return}
if(!/^0x[0-9a-fA-F]{64}$/.test(s))return void(stat.textContent="That is not a key.");
try{LS[CP_KEY+account.toLowerCase()]=s.toLowerCase()}catch{}cpUse(s.toLowerCase());cpPaint();pvRefresh();return}
if(a==="export"){prompt("Your note records — keep them with the key. Exit recipes cannot be rebuilt without them.",JSON.stringify(cpNotes));return}
if(a==="recover")return pvRun(async()=>{const f=await cpRecover();stat.textContent=f?"Recovered "+f+" deposit"+(f>1?"s":"")+".":"Nothing new on chain for this key."},"Scanning the pool…");
if(!n)return;
if(a==="settle")return pvRun(()=>cpSettleWrap(n),"Asking the relay…");
if(a==="exit")return pvRun(()=>cpExit(n),"Building the exit…");
if(a==="send")return pvRun(()=>cpSelfSend(n),"Sending the exit…");
if(a==="activate")return pvRun(()=>cpActivate(n),"Activating…");
if(a==="reclaim")return pvRun(()=>cpReclaim(n),"Reclaiming…")};
pv.onclick=()=>{sBlip();pvSet(!pvMode)};
let invOff=null;
const invIcons=()=>{const o=[],seen=new Set();
for(const t of TOKENS){const k=(t.sym||"").toLowerCase();
if(!k||seen.has(k)||t.addr===ZERO)continue;seen.add(k);o.push({s:t.sym,h:t.icon,l:!!t.launched})}
return o};
const invWei=n=>`<svg width="${n}" height="${n}" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges"><path d="M3 1h1v1H3zM5 2h1v1H5zM10 2h1v1h-1zM12 1h1v1h-1zM4 4h8v1H4zM3 5h10v1H3zM2 6h12v3H2zM3 9h10v1H3zM4 10h8v1H4zM5 11h2v1H5zM9 11h2v1H9zM2 12h3v1H2zM7 12h2v1H7zM11 12h3v1h-3zM1 13h2v1H1zM13 13h2v1h-2zM3 6h3v2H3zM10 6h3v2h-3z"/></svg>`;
function invPlay(){
if(invOff)return;
const host=document.querySelector(".card");if(!host)return;
const W=host.clientWidth,H=host.clientHeight,COLS=6,ROWS=4,IW=22,IH=20,SW=22,SH=20;
const mk=(c,h)=>{const d=document.createElement("div");d.className=c;if(h)d.innerHTML=h;return d};
const w=mk("inv");w.tabIndex=0;w.setAttribute("role","application");
w.setAttribute("aria-label","Token invaders. Arrow keys move, space fires, escape leaves.");
host.appendChild(w);
const hud=mk("invh");w.appendChild(hud);
hud.addEventListener("pointerdown",e=>{const k=e.target.className;
if(k==="invq"){e.stopPropagation();e.preventDefault();sBlip();quit()}
else if(k==="invm"){e.stopPropagation();e.preventDefault();mint()}
else if(k==="invsh"){e.stopPropagation();e.preventDefault();sBlip();shareMsg="";shareScore()}});
const ship=mk("invs",ETH_ICON);w.appendChild(ship);
const pool=invIcons();
let al=[],bu=[],bo=[],sx=(W-SW)/2,lf=0,rt=0,sc=0,lv=3,wv=1,ov=0,dir=1,sa=0,fa=-1e9,rq=0,lt=performance.now();
let tEnd=0,minted="",minting=0,mintMsg="",shareMsg="",runId=0;
const pos=(n,x,y)=>{n.style.transform=`translate(${x.toFixed(1)}px,${y.toFixed(1)}px)`};
const cur=pool.filter(t=>!t.l),lau=pool.filter(t=>t.l);
const face=(r,c,i)=>{const back=r<ROWS-1;
const src=back?(cur.length?cur:pool):(lau.length?lau:(cur.length?cur:pool));
const t=src.length?src[(back?i:c)%src.length]:null;
return(!t||back&&i%3===0&&src.length<COLS*(ROWS-1))?{s:"WEI",h:invWei(20)}:t};
const WAVE_MS=45000;
const left=()=>Math.max(0,Math.ceil((tEnd-performance.now())/1000));
const born=()=>{for(const a of al)a.node.remove();al=[];
tEnd=performance.now()+WAVE_MS;
const px=(W-COLS*IW)/2;
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){const t=face(r,c,r*COLS+c),n=mk("invx",t.h);
n.title=t.s;w.appendChild(n);al.push({node:n,s:t.s,x:px+c*IW,y:18+r*IH,c})}
dir=1};
let hudWas="";
const hud2=()=>{const t=left();
const html=(ov?`<span>game over \u00b7 ${sc}</span><span class="${minted||minting?"invmsg":"invm"}">`
+(minted?`${minted}`:minting?"minting\u2026":mintMsg?`${mintMsg}`:sc>0?"mint this score":"tap to play again")+`</span>`+(sc>0&&!minting?`<span class="invsh">${shareMsg||"share"}</span>`:"")
:`<span>${"\u25b2".repeat(Math.max(0,lv))}</span>`
+`<span${t<=10?' style="color:var(--w)"':""}>${t}s</span>`
+`<span>wave ${wv} \u00b7 ${sc}</span>`)
+`<span class="invq" title="Back to swapping (esc)">\u2715</span>`;
if(html===hudWas)return;
hudWas=html;hud.innerHTML=html};
const fire=()=>{const t=performance.now();
if(ov||bu.length||t-fa<120)return;fa=t;sPew();const n=mk("invb");w.appendChild(n);
bu.push({node:n,x:sx+SW/2-1,y:H-SH-12})};
const bomb=()=>{if(!al.length)return;const m=new Map();
for(const a of al){const p=m.get(a.c);if(!p||a.y>p.y)m.set(a.c,a)}
const f=[...m.values()],a=f[(Math.random()*f.length)|0],n=mk("invd");
w.appendChild(n);bo.push({node:n,x:a.x+IW/2,y:a.y+IH})};
const ht=(ax,ay,aw,ah,bx,by,bw,bh)=>ax<bx+bw&&ax+aw>bx&&ay<by+bh&&ay+ah>by;
const end=()=>{ov=1;sFail();hud2()};
const step=(dt,t)=>{
if(lf)sx-=190*dt;if(rt)sx+=190*dt;
sx=Math.max(0,Math.min(W-SW,sx));pos(ship,sx,H-SH-6);
const per=Math.max(90,620-(COLS*ROWS-al.length)*22-(wv-1)*60);
if(t-sa>per){sa=t;let e=0;
for(const a of al){const nx=a.x+dir*6;if(nx<0||nx+IW>W)e=1}
for(const a of al){if(e)a.y+=8;else a.x+=dir*6;pos(a.node,a.x,a.y)}
if(e)dir*=-1;
if(Math.random()<.5+wv*.08)bomb();
if(al.some(a=>a.y+IH>=H-SH-6))return end()}
if(tEnd&&performance.now()>tEnd){tEnd=performance.now()+WAVE_MS;lv--;sFail();
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end()}
for(const b of bu){b.y-=260*dt;pos(b.node,b.x,b.y)}
for(const b of bo){b.y+=110*dt;pos(b.node,b.x,b.y)}
for(let i=bu.length-1;i>=0;i--){const b=bu[i];
if(b.y+7<0){b.node.remove();bu.splice(i,1);continue}
const j=al.findIndex(a=>ht(a.x,a.y,IW-2,IH-2,b.x,b.y,2,7));
if(j>=0){const a=al[j],g=a.node;g.classList.add("pop");setTimeout(()=>g.remove(),120);
al.splice(j,1);sc+=a.s==="WEI"?25:10;b.node.remove();bu.splice(i,1);
if(al.length)sHit();else{sc+=100+left()*10;wv++;born();sGot()}}}
for(let i=bo.length-1;i>=0;i--){const b=bo[i];
if(b.y>H){b.node.remove();bo.splice(i,1);continue}
if(ht(sx,H-SH-6,SW,SH,b.x,b.y,2,6)){b.node.remove();bo.splice(i,1);lv--;
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end();sFail()}}
hud2()};
const loop=()=>{const t=performance.now(),dt=Math.min(.05,(t-lt)/1e3);lt=t;
if(!ov)step(dt,t);rq=requestAnimationFrame(loop)};
function quit(){if(rq)cancelAnimationFrame(rq);rq=0;w.remove();
document.removeEventListener("keydown",kd,true);document.removeEventListener("keyup",ku,true);
invOff=null}
const again=()=>{runId++;ov=0;sc=0;lv=3;wv=1;minted="";mintMsg="";shareMsg="";born();hud2()};
const shareScore=async()=>{
const what=(minted?`I scored ${sc} in zSwap invaders and claimed ${minted}`
:`I scored ${sc} in zSwap invaders`)+" \u{1F47E}";
const u="https://"+SHARE_HOST+(minted?"/#score="+minted:"");
const href="https://x.com/intent/post?text="+encodeURIComponent(what)+"&url="+encodeURIComponent(u);
let win=null;try{win=window.open(href,"_blank")}catch{}
if(win){try{win.opener=null}catch{}return}
shareMsg="copied";
try{await navigator.clipboard.writeText(what+" "+u)}catch{shareMsg="could not open x.com"}
hud2()};
const mint=async()=>{
if(!ov||minting||minted||!sc)return;
mintMsg="";
if(!account){mintMsg="connect to mint\u2026";hud2();
try{await connect()}catch{}
if(!account){mintMsg="wallet not connected";hud2();return}
mintMsg=""}
minting=1;hud2();
const r=runId;
try{await checkWallet();
const lbl=sc+"-w"+wv+"-"+Math.random().toString(36).slice(2).padEnd(4,"0").slice(0,4);
const lt=strTail(lbl),st=strTail(String(sc));
const req={from:account,to:SCOREM,value:"0x0",
data:"0x"+SEL_SCLAIM+pad32("60")+encUint(96+lt.length/2)+encAddr(account)+lt+st};
const ret=await rpc(C,[req,L]);
if(strip0x(ret||"").length!==64)throw Error("The minter did not answer \u2014 check the wallet is on Ethereum mainnet.");
await settle(await sendTx([req]));
if(r===runId){minted=lbl+".arcade.wei";sGot()}}catch(e){if(r===runId){try{console.error("zSwap mint failed:",e)}catch{}
mintMsg=String(isRejection(e)?"mint cancelled":explain(e)).replace(/[<>&"'`]/g,"").slice(0,60)||"the mint failed";sFail()}}
minting=0;hud2()};
function kd(e){if(e.key==="Escape"){e.preventDefault();quit();return}
if(e.key==="ArrowLeft"){lf=1;e.preventDefault()}
else if(e.key==="ArrowRight"){rt=1;e.preventDefault()}
else if(e.key===" "||e.key==="Enter"){e.preventDefault();ov?again():fire()}}
function ku(e){if(e.key==="ArrowLeft")lf=0;if(e.key==="ArrowRight")rt=0}
document.addEventListener("keydown",kd,true);document.addEventListener("keyup",ku,true);
let tp=0;
w.addEventListener("pointerdown",e=>{tp=1;sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2));ov?again():fire()});
w.addEventListener("pointermove",e=>{if(tp)sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2))});
w.addEventListener("pointerup",()=>{tp=0});
invOff=quit;born();hud2();w.focus();sOpen();rq=requestAnimationFrame(loop);
}
(()=>{let tm=0,hd=0;
const cn=()=>{if(tm)clearTimeout(tm);tm=0};
wn.addEventListener("pointerdown",()=>{hd=0;cn();tm=setTimeout(()=>{tm=0;hd=1;invPlay()},550)});
for(const ev of ["pointerup","pointerleave","pointercancel"])wn.addEventListener(ev,cn);
wn.addEventListener("click",e=>{if(hd){e.preventDefault();e.stopPropagation();hd=0}},true);
wn.addEventListener("contextmenu",e=>e.preventDefault());
})();
wnName.oninput=()=>{wnName.value=wnName.value.toLowerCase();wnQuote()};
wnTld.onchange=wnQuote;
wnEnter.onclick=async()=>{
if(!account)return void connect();
if(!wnEnter.dataset.id){wnRollEl.textContent="Type a paid .wei name you own in the box above, and it can enter the roll.";return}
wnEnter.disabled=true;
try{
await checkWallet();
const req={from:account,to:WROLL,value:"0x0",data:"0x"+SEL_RENTER+pad32(wnEnter.dataset.id)+encUint(0)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
await wnRoll();
}catch(e){err(e)}
wnEnter.disabled=false};
wnGo.onclick=async()=>{
if(wnGo.disabled)return;
if(!account)return void connect();
wnGo.disabled=true;
try{
await checkWallet();
const p=wnPend();
if(p){
if(!p.at){
if(Math.floor(Date.now()/1000)-(p.since||0)<=600)throw Error("The commitment has not confirmed yet.");
wnSave(null);stat.textContent="That commitment never reached the chain. Commit again.";
await wnQuote();return}
const age=Math.floor(Date.now()/1000)-p.at;
if(age<WN_MIN)throw Error(`Wait ${WN_MIN-age} more seconds.`);
if(age>=WN_MAX){wnSave(null);stat.textContent="That commitment lapsed. Commit again.";wnQuote();return}
const cost=await wnCost(p.label);
const req={from:account,to:WNS,value:toHex(cost),
data:"0x"+SEL_WREVEAL+pad32("40")+pad32(p.secret)+strTail(p.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
wnSave(null);
stat.textContent=`${p.label}.wei is yours.`;
wnName.value="";
}else if(wnQ&&wnQ.free){
const req={from:account,to:WREG,value:"0x0",
data:"0x"+SEL_WSUB+pad32(IDWEI)+pad32("40")+strTail(wnQ.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
stat.textContent=`${wnQ.label}.id.wei is yours.`;
wnName.value="";
}else if(wnQ){
const sec=wnSecret();
const req={from:account,to:WNS,value:"0x0",
data:"0x"+SEL_WCOMMIT+pad32(wnHash(wnQ.label,account,sec))};
await rpc(C,[req,L]);
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000)});
let h;
try{h=await sendTx([req])}
catch(e){
if(e&&(e.code===4001||/user (rejected|denied)|rejected the request|denied transaction/i.test(e.message||"")))wnSave(null);
throw e}
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000),tx:h});
try{await settle(h)}catch(e){if(/tx reverted/.test(e&&e.message||""))wnSave(null);throw e}
await wnStamp();
stat.textContent="Committed. Register the name in a minute.";
}
await wnQuote();await wnRoll();refreshBalance();
}catch(e){err(e)}
wnSync()};
lnGo.onclick=async()=>{
stat.textContent="";
if(lnGo.disabled)return;
if(!account)return void connect();
lnGo.disabled=true;
try{
await checkWallet();
if(lnKind.value==="cause")return await cauGo();
const name=lnName.value.trim(),sym=lnSym.value.trim().toUpperCase();
if(!name||!sym)throw Error("A name and a symbol, please.");
const utf8=v=>new TextEncoder().encode(v).length;
if(utf8(name)>64)throw Error(`That name is ${utf8(name)} bytes and the limit is 64.`);
if(utf8(sym)>16)throw Error(`That symbol is ${utf8(sym)} bytes and the limit is 16.`);
const supRaw=lnSupply.value.replace(/[, ]/g,"");
if(!/^\d+(\.\d+)?$/.test(supRaw)||!(Number(supRaw)>0))throw Error("Supply has to be a positive number.");
const supply=parseUnits(supRaw,18);
const typed=Number(lnAlloc.value||0);
if(!(typed>=0)||typed*100>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
const alloc=Math.round(typed*100);
if(!(alloc>=0)||alloc>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
if(typed>0&&alloc===0)throw Error("Allocation is priced in whole basis points — the smallest non-zero value is 0.01%.");
const mcap=parseUnits(String(lnMcap.value||"").trim(),18);
if(mcap<LN_MIN_MCAP)throw Error("That opening valuation is too small to price.");
const pooled=supply-supply*BigInt(alloc)/10000n;
if(pooled<LN_MIN_POOLED)throw Error("Too little supply reaches the pool to price a market.");
if(pooled>mcap*10n**36n)throw Error("That supply is too large for that opening valuation \u2014 raise the valuation.");
if(pooled*10n**24n<mcap)throw Error("That opening valuation is too large for that supply \u2014 lower it, or mint more.");
const owner=await lnOwner();
stat.textContent="Launching\u2026";
const req={from:account,to:PLAUNCH,value:"0x0",
data:encLaunch(name,sym,"",supply,BigInt(alloc),mcap,owner,lnArtBytes,lnArtMime)};
const sim=await rpc(C,[req,L]);
const candidate=/^0x[0-9a-fA-F]{128}$/.test(sim||"")?"0x"+strip0x(sim).slice(24,64):null;
const receipt=await waitTx(await sendTx([req]));sGot();
lnSet(false);
lnName.value="";lnSym.value="";lnSupply.value="";lnAlloc.value="";
const topic=kecStr("Launched(address,address,address,uint256,uint256,uint256)");
const lg=(receipt.logs||[]).find(l=>(l.address||"").toLowerCase()===PLAUNCH.toLowerCase()
&&(l.topics||[])[0]&&l.topics[0].toLowerCase()===topic.toLowerCase());
let token=lg?"0x"+strip0x(lg.topics[1]).slice(24):null;if(token&&!isAddr(token))token=null;
if(!token&&candidate){
try{
const c=await rpc(C,[{to:PLAUNCH,